DEV Community

Cover image for The nginx security headers checklist (2026), and how to verify them
North Dev
North Dev

Posted on

The nginx security headers checklist (2026), and how to verify them

Most nginx configs are one add_header away from leaking more than they should. Here's a 2026 security-header checklist you can actually verify — plus the inheritance gotcha that silently disables half of them.

The headers that matter

  • Strict-Transport-Security — force HTTPS for future visits. max-age=63072000; includeSubDomains; preload.
  • X-Content-Type-Options: nosniff — stop MIME sniffing.
  • Content-Security-Policy — the big one. Even a minimal frame-ancestors 'self' (clickjacking) and a scoped default-src beats nothing. Build it per app.
  • Referrer-Policy: strict-origin-when-cross-origin — stop leaking full URLs.
  • X-Frame-Options: SAMEORIGIN — legacy clickjacking guard (CSP frame-ancestors supersedes it, but harmless to keep).
  • Drop server_tokens off to stop advertising your exact nginx version.

The gotcha that breaks everything

add_header does not inherit into a block that defines its own add_header. If you set security headers at server level and then add a single add_header inside a location (say, a cache header), every security header vanishes for that location — silently, no error.

Fix: define headers once at the broadest scope and don't re-declare add_header in child blocks, or repeat them (an include snippet of headers is the clean pattern).

Don't serve your dotfiles

A classic exposure — .git/, .env, .htpasswd reachable over HTTP:

location ~ /\.(?!well-known) { deny all; return 404; }
Enter fullscreen mode Exit fullscreen mode

Verify, don't assume

Check what you actually send:

curl -sI https://example.com | grep -iE 'strict-transport|content-security|x-content-type|referrer-policy'
Enter fullscreen mode Exit fullscreen mode

If a header you configured isn't there, you probably hit the add_header inheritance trap.


I put these rules (plus TLS protocol/cipher and server_name checks) into nginx-doctor — point it at your config and it prints each finding with the fix. Zero-config Go binary, read-only.

Top comments (0)