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.
How to check your nginx config
- On your server, print the fully assembled config:
sudo nginx -T. That resolves everyinclude, whichcat /etc/nginx/nginx.confdoes not. - Paste the output above. The review runs as you type, in your browser, and nothing is uploaded.
- Read the findings worst-first. Each one shows the directive, the line, the block it lives in, and what it should be.
- 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.
- Switch to the Hardening patch tab. Apply the safe lines first, stage the rest one at a time, then run
sudo nginx -tandsudo 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
| Directive | Default if unset | What you want | Why |
|---|---|---|---|
| server_tokens | on | off | The default puts your exact nginx version in every response header and error page, which tells a scanner which CVEs to try. |
| add_header | inherited | ... always | Inherited only if the current level sets none of its own. One header in a location silently drops every parent header there. |
| ssl_protocols | build default | TLSv1.2 TLSv1.3 | TLS 1.0 and 1.1 were deprecated by RFC 8996 in 2021 and are still enabled by default on older builds. |
| alias | n/a | slashes matched | A location without a trailing slash paired with an alias that has one lets a request escape the directory. |
| autoindex | off | off | On, any directory with no index file becomes a browsable listing of everything in it. |
| client_max_body_size | 1m | set deliberately | Too low breaks uploads with a 413, too high invites memory and disk exhaustion. |
| access_log | on | a real path | Switched off, a request leaves no trace, and there is nothing to investigate after an incident. |
| ssl_session_tickets | on | off | nginx-managed ticket keys are never rotated while the process lives, which undoes forward secrecy. |
| limit_req | unset | a zone | Without it one client can issue as many requests as it likes. |
Frequently asked questions
Related developer tools
Nginx Config Generator
Generate an nginx server block for a reverse proxy, static site, SPA, or PHP - with SSL.
Cron Expression Generator
Build and decode crontab schedules with a plain-English preview.
SSL Certificate & CSR Decoder
Decode certificates, CSRs, and chains - expiry, SANs, fingerprints, chain order. Nothing uploaded.
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