How to use the Nginx Config Analyzer

  1. Paste a site file from /etc/nginx/sites-available/, a server block or nginx.conf, or open the file.
  2. Fix any syntax error first; the analyzer names the line, like nginx -t would.
  3. Work down the findings, most severe first, using the directive each one gives.
  4. 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

CheckSeverityThe fix
ssl_protocols with SSLv3, TLSv1 or TLSv1.1Critical or highssl_protocols TLSv1.2 TLSv1.3;
RC4, 3DES, export or NULL ciphers in ssl_ciphersHighMozilla's intermediate cipher list
location /static with alias /path/static/;HighGive both a trailing slash
$uri in return, rewrite, add_header or proxy_passHighUse $request_uri
proxy_pass to a host taken from the requestHighMap allowed values to fixed upstreams
listen 443 without sslHighlisten 443 ssl;
HTTPS without Strict-Transport-SecurityMediumadd_header Strict-Transport-Security "max-age=31536000" always;
add_header in a location dropping inherited headersMediumRepeat them in that block
autoindex onMediumautoindex off;
if in a location doing more than return or rewriteMediumtry_files, map or separate locations
The site also answering on plain HTTPMediumreturn 301 https://$host$request_uri;
Missing X-Content-Type-Options, X-Frame-Options, Referrer-PolicyLowThe add_header lines
Security headers without alwaysLowAppend always
proxy_pass without Host, X-Forwarded-For, X-Forwarded-ProtoLowThe proxy_set_header lines
server_tokens on or unsetLowserver_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 connections

If 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.