Free tool · Runs in your browser

Nginx Config Analyzer: Grade and Harden Your nginx.conf

Paste your nginx config and get a graded A to F security review with the exact line to change. Catches the add_header inheritance trap that silently drops your security headers, alias path traversal, deprecated TLS versions, and weak ciphers. Nothing is uploaded.

Your config is analyzed entirely in this browser tab. Nothing is uploaded, logged, or sent anywhere, which matters because an nginx.conf exposes your upstream hosts and web root layout.

Try:

How to check your nginx config

  1. On your server, print the fully assembled config: sudo nginx -T. That resolves every include, which cat /etc/nginx/nginx.conf does not.
  2. Paste the output above. The review runs as you type, in your browser, and nothing is uploaded.
  3. Read the findings worst-first. Each one shows the directive, the line, the block it lives in, and what it should be.
  4. Open the Header map tab. It shows, per location, which security headers actually apply and which were silently dropped. This is the part a config review by eye almost always misses.
  5. Switch to the Hardening patch tab. Apply the safe lines first, stage the rest one at a time, then run sudo nginx -t and sudo systemctl reload nginx.

The add_header rule that silently deletes your security headers

This is the single most common way a hardened-looking nginx config is not hardened. The documentation states it plainly: add_header directives are inherited from the previous configuration level if and only if there are no add_header directives defined on the current level.

It is all or nothing. Set HSTS, CSP, X-Frame-Options and X-Content-Type-Options at server level, then add one unrelated header inside a location, and all four stop applying to that location. Not the one you added. All of them.

The config still reads as though the headers apply everywhere. nginx prints no warning, nginx -t is happy, and the only way to notice is to request a URL inside that location and read the response headers. A header scanner pointed at your homepage will report a clean bill of health while /api serves nothing at all.

The fix is to re-declare the full set inside every block that adds a header of its own, or to put them in one file and include it in both places. There is no merge option. The Header map tab shows you exactly which blocks are affected.

Alias path traversal, and the missing slash that causes it

A prefix location matches anything that starts with the prefix, then nginx strips it and appends whatever is left to the alias. When the location has no trailing slash and the alias does, those two facts combine into a directory traversal:

location /files with alias /var/www/files/; means a request for /files../secrets.env resolves to /var/www/secrets.env. The location looks scoped to one directory. It is not.

The same off-by-slash affects proxy_passwhen the target has a URI part, which lets a request reach backend paths the location was supposed to hide. Both were popularised by Orange Tsai's "Breaking Parser Logic" research and both are still common.

Fix it by matching the slashes on both sides, or by using root, which appends the full URI and does not have this failure mode.

Why your headers are missing on error pages

By default add_header only applies to a fixed list of response codes: 200, 201, 204, 206, 301, 302, 303, 304, 307 and 308. Anything else, including every 4xx and 5xx, gets no header at all.

So a 404 page, a 500 from a crashed upstream, or a 403 from an access rule is served with no CSP, no X-Frame-Options and no HSTS. Error pages are also the most likely place for a template to reflect part of the request back to the user, which is exactly when a CSP matters.

The always parameter removes the response-code restriction. Every security header you set should have it. In the Header map tab, a header marked with an asterisk is one that is missing it.

What a config file cannot tell you

This tool reads text, and it is honest about the limits of that. It cannot tell you whether your certificate expires next week, what your server actually sends back, whether a stray .env is sitting in the web root, whether nginx itself needs patching, or what flags your application sets on its cookies. None of that is in the config.

Those checks need the running machine. The web server fix library documents all 36 of them, including the ones this tool covers, and CtrlOps runs the whole set over SSH across every server you manage.

Building a new config rather than reviewing one? The Nginx Config Generator writes a server block with these defaults already correct. For the SSH side of the same server, the sshd_config Analyzer does the equivalent review.

The nginx directives that decide how exposed you are

DirectiveDefault if unsetWhat you wantWhy
server_tokensonoffThe default puts your exact nginx version in every response header and error page, which tells a scanner which CVEs to try.
add_headerinherited... alwaysInherited only if the current level sets none of its own. One header in a location silently drops every parent header there.
ssl_protocolsbuild defaultTLSv1.2 TLSv1.3TLS 1.0 and 1.1 were deprecated by RFC 8996 in 2021 and are still enabled by default on older builds.
aliasn/aslashes matchedA location without a trailing slash paired with an alias that has one lets a request escape the directory.
autoindexoffoffOn, any directory with no index file becomes a browsable listing of everything in it.
client_max_body_size1mset deliberatelyToo low breaks uploads with a 413, too high invites memory and disk exhaustion.
access_logona real pathSwitched off, a request leaves no trace, and there is nothing to investigate after an incident.
ssl_session_ticketsonoffnginx-managed ticket keys are never rotated while the process lives, which undoes forward secrecy.
limit_requnseta zoneWithout it one client can issue as many requests as it likes.
nginx security FAQ

Frequently asked questions

Yes. The analysis runs entirely in your browser using JavaScript on this page. Your config is never uploaded, logged, or transmitted, and there is no server to store it. That is worth checking on any tool like this: an nginx.conf names your upstream hosts and ports, your web root layout, and often your auth paths. The main hosted alternative runs gixy server-side, which means your config is uploaded.
Because nginx inherits add_header from a parent level only if the current level defines none of its own. It is all or nothing. If a location block adds a single header, every header set at server or http level stops applying there, including HSTS and CSP. nginx gives no warning and nginx -t passes. The fix is to re-declare the whole set inside that block, or move them into a file you include in both places.
A prefix location matches anything starting with the prefix, and nginx appends whatever follows to the alias path. If the location has no trailing slash but the alias does, a request like /files../config.php escapes the intended directory and reads files above it. Fix it by matching the trailing slashes on both sides, or by using root instead of alias, which does not have this failure mode. The same off-by-slash applies to proxy_pass when the target has a URI part.
Prefer sudo nginx -T. It prints the fully assembled configuration with every include resolved, which is what nginx actually runs. Pasting a single file is still useful and gives you line numbers you can map back, but any include in it could set or override something this tool then reports incorrectly. The tool warns you when it sees an include it cannot follow.
Without it, add_header only applies to responses with a status of 200, 201, 204, 206, 301, 302, 303, 304, 307 or 308. Every 4xx and 5xx response is served without the header. That means your CSP and X-Frame-Options are absent from exactly the error pages most likely to reflect user input back. Add always to every security header.
The patch is split for that reason. The safe block changes nothing a visitor sees: server_tokens, logging, timeouts, dot-file denial. The second block genuinely can be noticed: a strict Content-Security-Policy blocks inline scripts until you add nonces, HSTS is cached by browsers and cannot be withdrawn quickly, tightening ssl_protocols cuts off old clients, and a rate limit set too low returns 503 to real traffic. Apply those one at a time and watch your logs. Always run sudo nginx -t before reloading.
No, and this is where reviewing a config by eye goes wrong. An absent directive runs nginx's compiled-in default, and several of those defaults are the insecure value. server_tokens defaults to on, so your version is advertised unless you say otherwise. ssl_session_tickets defaults to on. client_max_body_size defaults to 1 MB. This tool marks findings that come from a default so you can tell the two cases apart.
Because the advice has gone stale, and CtrlOps publishes that on its known-issues page. Let's Encrypt stopped putting OCSP URLs in its certificates in May 2025 and switched its responders off in August 2025. On a Let's Encrypt certificate, ssl_stapling on now does nothing except log a warning that there is no responder URL. Check your certificate first with openssl x509 -noout -ocsp_uri -in cert.pem. If that is empty, stapling does not apply to you.
nginx -t checks that the config parses and that referenced files exist. It says nothing about whether the config is secure. A file with TLSv1 enabled, autoindex on, root login to the status page and no security headers passes nginx -t cleanly. This tool checks the security properties instead, and assumes the syntax is broadly valid. Run both.
No. It reviews configuration text for one service. It cannot see certificate expiry, file permissions in the web root, which user the worker processes run as, whether nginx needs patching, or what the server actually returns. The web server fix library covers all 36 of those checks, and CtrlOps automates them across a fleet over SSH.
One config is easy. Twenty is the problem.

Review one file here, audit every server there.

CtrlOps runs 25 security audits over SSH across your whole fleet, scores each server, and fixes findings with approval-gated AI. Your keys never leave your machine.

Start instantly· No credit card· No sneaky autorenewals