How to use the Nginx Config Analyzer
- Paste a site file from /etc/nginx/sites-available/, a server block or nginx.conf, or open the file.
- Fix any syntax error first; the analyzer names the line, like nginx -t would.
- Work down the findings, most severe first, using the directive each one gives.
- Run sudo nginx -t, then sudo systemctl reload nginx.
What this Nginx config check looks for
An Nginx configuration can pass nginx -t and still be insecure or subtly broken: nginx -t only proves the syntax is valid, not that TLS 1.0 is off, that security headers reach every page, or that an alias cannot be walked out of. This analyzer parses the configuration the way Nginx does and reports those problems with a line number, a reason and the directive to write.
It reads directives, blocks, quoted strings, comments and variables. include lines are listed but not followed, so paste an included file separately to check it.
What it checks
| Check | Severity | The fix |
|---|---|---|
ssl_protocols with SSLv3, TLSv1 or TLSv1.1 | Critical or high | ssl_protocols TLSv1.2 TLSv1.3; |
RC4, 3DES, export or NULL ciphers in ssl_ciphers | High | Mozilla's intermediate cipher list |
location /static with alias /path/static/; | High | Give both a trailing slash |
$uri in return, rewrite, add_header or proxy_pass | High | Use $request_uri |
proxy_pass to a host taken from the request | High | Map allowed values to fixed upstreams |
listen 443 without ssl | High | listen 443 ssl; |
HTTPS without Strict-Transport-Security | Medium | add_header Strict-Transport-Security "max-age=31536000" always; |
add_header in a location dropping inherited headers | Medium | Repeat them in that block |
autoindex on | Medium | autoindex off; |
if in a location doing more than return or rewrite | Medium | try_files, map or separate locations |
| The site also answering on plain HTTP | Medium | return 301 https://$host$request_uri; |
Missing X-Content-Type-Options, X-Frame-Options, Referrer-Policy | Low | The add_header lines |
Security headers without always | Low | Append always |
proxy_pass without Host, X-Forwarded-For, X-Forwarded-Proto | Low | The proxy_set_header lines |
server_tokens on or unset | Low | server_tokens off; |
It also notes things that are not wrong in themselves: gzip on HTTPS pages (BREACH), uploads over the 1 MB default client_max_body_size, HTTP/2 not enabled, proxy_http_version 1.1 without WebSocket headers, and what server_name _ and default_server really do.
The add_header trap
add_header is inherited from the server block only by locations that have no add_header of their own. Add a single Cache-Control header to one location and every security header set on the server disappears from that location's responses:
server {
add_header X-Frame-Options "SAMEORIGIN" always;
location /assets/ {
add_header Cache-Control "public, max-age=31536000"; # X-Frame-Options is gone here
}
}Repeat the server's headers inside the location, or keep them in a snippet and include it in both places. proxy_set_header follows the same rule.
The alias off-by-slash
With location /static { alias /var/www/app/static/; }, a request for /static../.env maps to /var/www/app/static/../.env, the file one level up. The location and the alias should both end in a slash, or neither should be an alias at all: root /var/www/app; with location /static/ is simpler.
Header injection through $uri
$uri holds the decoded path, so %0d%0a in a request becomes a real line break. return 301 https://$host$uri; then lets anyone add a header, such as Set-Cookie, to your redirect. $request_uri is the raw, still-encoded path and is safe there.
Test and reload
sudo nginx -t # syntax and file checks for every included file
sudo systemctl reload nginx # apply without dropping connectionsIf nginx -t fails, the old configuration keeps running after a reload; a restart or a reboot would not start at all. Check the result from outside with curl -I https://example.com and look for the headers you added.
Let Shipown write and test it
The Nginx config generator writes a server block with the HTTPS redirect, security headers and proxy headers already in. Shipown writes the virtual host for each app it deploys, tests it with nginx -t before reloading, asks Certbot for the certificate, and puts the previous configuration back if Nginx rejects the new one. See deploying with Shipown.
Frequently asked questions
Does nginx -t check for security problems?
No. It checks syntax and that referenced files exist. A configuration that allows TLS 1.0, lists directories or drops security headers passes nginx -t.
Why do my add_header lines not appear on some pages?
A location with any add_header of its own inherits none from the server block. Error responses also lose headers that are added without the always parameter.
What is the Nginx alias path traversal?
When a location has no trailing slash but its alias does, a request like /static../ maps to the folder above the alias. Give both a trailing slash.
Which ssl_protocols should I use?
TLSv1.2 TLSv1.3. TLS 1.0 and 1.1 were deprecated by RFC 8996 and no current browser needs them. It is also the default since nginx 1.23.4.
Does the analyzer follow include files?
No. It lists them and analyses only the text you paste. Paste an included file on its own to check it.
Is my configuration sent anywhere?
No. Parsing and every check run in your browser.