Nginx

Nginx Security Headers Best Practices for Web Applications

4 min read by DebuggedIt

Quick answer

Quick answer: configure Nginx to send a baseline of security headers: Strict-Transport-Security, X-Content-Type-Options, a framing control, Referrer-Policy,...

Quick answer: configure Nginx to send a baseline of security headers: Strict-Transport-Security, X-Content-Type-Options, a framing control, Referrer-Policy, and Permissions-Policy, and roll out a Content-Security-Policy gradually. Use the always parameter so headers appear on error responses too, and remember that add_header in a nested block replaces inherited headers instead of merging them.

Which Security Headers Should You Set?

Security headers instruct browsers to apply protections that your application cannot enforce alone. They are cheap to add and reduce the impact of common web issues like clickjacking, content type confusion, and downgrade attacks. A reasonable baseline covers the following headers:

  • Strict-Transport-Security: tells browsers to use HTTPS only for your domain for a period of time.
  • X-Content-Type-Options with nosniff: stops browsers from guessing content types, which prevents some script injection tricks.
  • X-Frame-Options or the frame-ancestors directive: controls which sites may embed your pages in frames, defending against clickjacking.
  • Referrer-Policy: limits how much of the URL is sent to other sites in the Referer header.
  • Permissions-Policy: disables browser features such as camera or geolocation that your site does not use.
  • Content-Security-Policy: restricts where scripts, styles, and other resources can load from, and is the strongest and hardest to deploy.

Each header has options and edge cases. Treat the list above as a starting point and check current browser documentation for supported directives, because the set evolves and some older headers have been superseded.

Placement also matters. Setting headers at the server level gives you one place to manage the baseline, while location-specific headers, such as a tighter policy for an admin area or a relaxed one for a public asset directory, should repeat the full baseline. Document which headers are expected so reviewers can spot omissions.

How Do You Configure Them in Nginx?

Use the add_header directive with the always parameter. Without it, Nginx adds headers only to successful and redirect responses, so error pages would be unprotected:

server_tokens off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

The most common trap is inheritance. Add_header directives are inherited from the previous level only if the current level defines none of its own. If a location block adds even one header, it discards every header set at the server level. This silently removes protections on those locations. Repeat the full set in each block that adds headers, or define them in a shared include file and include it wherever needed.

Setting server_tokens off hides the Nginx version in responses and error pages. It is minor hardening, since it does not fix vulnerabilities, but it avoids advertising your exact version to scanners.

How Do You Roll Out HSTS and CSP Safely?

HSTS is powerful and difficult to undo. Once a browser has seen the header, it will refuse plain HTTP for the whole max-age period. Start with a short value, confirm that every subdomain you include actually supports HTTPS, and increase gradually. Be especially careful with the preload option, which submits your domain to browser-maintained lists and is hard to reverse.

Content-Security-Policy needs a staged approach because it can break legitimate functionality. Begin in report-only mode, which sends violation reports without blocking anything, and fix or allow the sources your application really uses. Avoid broad unsafe allowances such as inline script permissions where possible, since they weaken the policy. Move to enforcement when reports are quiet.

Test headers after deployment with a request from the command line and a browser's developer tools, including on error responses and on each location. Security headers are one layer, so combine them with input validation, secure cookies, and regular dependency updates.

Frequently Asked Questions

Why are my headers missing on some responses?

Either the always parameter is missing, so error responses lack them, or a nested block defines its own add_header and discards the inherited ones.

Is X-Frame-Options still needed?

The CSP frame-ancestors directive is the modern approach, but many sites keep X-Frame-Options for compatibility with older browsers. Check current browser support.

Can HSTS lock me out of my site?

If HTTPS breaks on a domain covered by a long HSTS policy, browsers will refuse to connect over HTTP until the policy expires. Start with short durations.

Should the application or Nginx set security headers?

Either works. Nginx is convenient for consistent baseline headers, while applications may need to set route-specific ones such as CSP with nonces.

Summary

Set a baseline of security headers with the always parameter, avoid the add_header inheritance trap, and roll out HSTS and CSP carefully. Verify the final responses on every location. Consult current browser and Nginx documentation for supported directives.