DEV Community

Cover image for HTTP security headers in ten minutes: a safe starter set for nginx and how to test it
Daniele Michelin
Daniele Michelin

Posted on Originally published at daniele-michelin.com

HTTP security headers in ten minutes: a safe starter set for nginx and how to test it

Security headers are instructions your server sends along with every page: “only use HTTPS”, “do not guess file types”, “do not let other sites put me in a frame”. The browser enforces them. They do not fix vulnerable code, but they shrink what an attacker can do with one, and they cost a few lines of configuration. Yet a quick scan of most small business sites shows an empty list.

The headers that matter

  • Strict-Transport-Security (HSTS): tells the browser to use only HTTPS for your domain for a given time. Stops downgrade and cookie-sniffing on hostile networks. Start with max-age=31536000; includeSubDomains.
  • Content-Security-Policy (CSP): lists where scripts, styles, images and frames may load from. It is the strongest defence against cross-site scripting, and the only one that takes real work.
  • X-Content-Type-Options: nosniff: the browser must trust the declared content type instead of guessing, which closes a class of upload-based attacks.
  • X-Frame-Options or CSP frame-ancestors: prevents other sites from embedding yours in a frame (clickjacking).
  • Referrer-Policy: controls how much of the URL leaks to other sites. strict-origin-when-cross-origin is a sane default.
  • Permissions-Policy: switches off browser features you do not use, such as camera, microphone or geolocation.
  • Cross-Origin-Opener-Policy: isolates your window from pop-ups opened by other origins.

A starter set for nginx

This is close to what runs on this site. Put it in the server block (or an included file):

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; frame-ancestors 'self'" always;
server_tokens off;
Enter fullscreen mode Exit fullscreen mode

Then check and reload: nginx -t && systemctl reload nginx.

Two mistakes that break sites

1. add_header disappears in nested blocks

In nginx, as soon as a location block contains its own add_header, it stops inheriting the ones from the server block. A cache header in one location can silently remove all your security headers there. Put the headers in one included file and repeat the include wherever a location sets its own headers. Always test a static file and a PHP or proxied URL, not only the home page.

2. A CSP that is too strict, switched on in one go

The CSP above is deliberately small. A real site often loads fonts, analytics or inline scripts, and a strict policy blocks them. Roll it out in two steps:

  1. Send Content-Security-Policy-Report-Only first. Nothing breaks, but the browser console lists what would have been blocked.
  2. Fix or allow-list what you find, then switch to the enforcing header.

Avoid 'unsafe-inline' in script-src: it removes most of the protection. If you have a single inline script, allow it by its hash ('sha256-…') instead.

Remove what leaks

server_tokens off; hides the nginx version. Also check for X-Powered-By from PHP or the hosting panel: it tells attackers which software to target, and it is easy to miss because it is added in a different layer than the rest of your headers.

Test it

From a terminal:

curl -sI https://example.com/ | grep -iE 'strict|content-security|x-frame|x-content|referrer|permissions|cross-origin|x-powered|server'
Enter fullscreen mode Exit fullscreen mode

Or paste the address into the free security header checker: it follows redirects, grades the result from A to F, and tells you which header is missing or too weak. No sign-up, nothing is stored.

A sensible order

  1. HSTS, nosniff, Referrer-Policy, Permissions-Policy: safe on almost any site, do them now.
  2. Frame protection: SAMEORIGIN unless you deliberately embed the site elsewhere.
  3. CSP in report-only mode, then enforcing.
  4. Re-test after every change to your proxy, panel or CDN, because each layer can add or drop headers.

Headers are not a replacement for patching. If you are on nginx, the latest posts cover current advisories; these headers are the cheap layer that makes the next one hurt less.


Originally published at daniele-michelin.com. More guides, free checks (SPF, TLS, headers) and an error-message lexicon: daniele-michelin.com/en/.

Top comments (0)