How to Configure Security Headers in Nginx: A Practical Guide With Examples

If your Nginx server is serving traffic without proper HTTP security headers, you are leaving the door open to XSS attacks, clickjacking, MIME sniffing, and protocol downgrade attacks. The good news? Hardening Nginx takes about 10 minutes and a few lines of config.

This guide gives you copy-pasteable Nginx security headers configurations, explains what each header protects against, and shows you how to verify everything works with free online scanners. No fluff, just practical config blocks you can deploy today.

Why Nginx Security Headers Matter

HTTP security headers are instructions sent from your server to the visitor’s browser telling it how to behave when handling your site’s content. They mitigate entire categories of attacks at the browser level, before malicious code can do damage.

Without them, even a perfectly coded web application can be hijacked through clickjacking iframes, mixed content injection, or referrer leakage. With them properly configured, you instantly raise your security posture and your score on tools like securityheaders.com and Mozilla Observatory.

nginx server security

The Right Way to Add Headers in Nginx

Before pasting anything, understand this critical gotcha: in Nginx, add_header directives are NOT inherited from parent blocks if a child block defines its own add_header. If you set headers in server {} but then add one inside location /api {}, the server-level headers disappear from that location.

The cleanest solution: put all your security headers in a single include file and reference it everywhere needed, or use the always parameter to ensure headers are sent even on error responses.

The Complete Nginx Security Headers Config Block

Here is the full block you can drop into your Nginx server {} configuration. Each header is explained in the sections below.

# /etc/nginx/snippets/security-headers.conf

# Force HTTPS for 2 years, include subdomains, allow preload
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;

# Block MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;

# Control referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Restrict browser features
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;

# Content Security Policy (tighten for your app)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;

# Cross-Origin policies (modern browsers)
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Hide Nginx version
server_tokens off;

Then in your site config:

server {
    listen 443 ssl http2;
    server_name example.com;

    include /etc/nginx/snippets/security-headers.conf;

    # rest of your config
}

Reload Nginx after editing:

sudo nginx -t && sudo systemctl reload nginx
nginx server security

Breakdown of Each Security Header

1. Strict-Transport-Security (HSTS)

Protects against: Protocol downgrade attacks and SSL stripping (man-in-the-middle).

HSTS tells browsers to only ever connect over HTTPS, even if a user types http://. The max-age is in seconds (63072000 = 2 years). The preload directive makes you eligible for the Chromium HSTS preload list.

Warning: Only enable includeSubDomains and preload once you are 100% sure every subdomain serves HTTPS. Reverting is painful.

2. X-Frame-Options

Protects against: Clickjacking attacks where your site is embedded in a malicious iframe.

Value Behavior
DENY Block all framing
SAMEORIGIN Only your own domain can frame the page

Note: This header is being superseded by CSP’s frame-ancestors directive, but keep it for legacy browser support.

3. X-Content-Type-Options

Protects against: MIME-type sniffing, where browsers guess content types and may execute uploaded files as scripts.

The only valid value is nosniff. Always enable it. There is no downside.

4. Referrer-Policy

Protects against: Leaking sensitive URLs (tokens, internal paths) to third-party sites via the Referer header.

Recommended values:

  • strict-origin-when-cross-origin (good default balance)
  • no-referrer (most private)
  • same-origin (strict, no referrer leaves your site)

5. Permissions-Policy

Protects against: Malicious or compromised scripts abusing powerful browser APIs (camera, mic, geolocation, etc.).

List each feature you want to disable with empty parentheses. The example above disables geolocation, microphone, camera, and payment APIs entirely. If your app needs one, change () to (self).

6. Content-Security-Policy (CSP)

Protects against: XSS, data injection, and unauthorized resource loading.

CSP is the most powerful and the most likely to break your site if misconfigured. Start in report-only mode:

add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;

Monitor violations for a week, then switch to enforcing mode. Common directives:

  • default-src 'self': only load resources from your own origin by default
  • script-src 'self': avoid 'unsafe-inline' and 'unsafe-eval' if possible
  • frame-ancestors 'self': modern replacement for X-Frame-Options
  • object-src 'none': blocks legacy Flash and plugin vectors

7. Cross-Origin-Opener-Policy & Cross-Origin-Resource-Policy

Protects against: Spectre-style side-channel attacks and cross-origin resource theft.

Setting both to same-origin isolates your browsing context from other origins. Required if you want to use SharedArrayBuffer or high-resolution timers.

Removing Headers That Leak Information

By default, Nginx exposes its version. Disable that and remove other leaky headers:

http {
    server_tokens off;
    more_clear_headers "Server";
    more_clear_headers "X-Powered-By";
}

The more_clear_headers directive requires the headers-more-nginx-module, available in most package repos as nginx-extras.

nginx server security

How to Test Your Nginx Security Headers

Once you’ve deployed the config, verify with these free scanners:

  1. securityheaders.com: gives you an A+ to F grade with specific recommendations
  2. Mozilla Observatory (observatory.mozilla.org): more thorough, scores TLS and headers together
  3. SSL Labs (ssllabs.com/ssltest): verifies HSTS and HTTPS configuration
  4. CSP Evaluator by Google (csp-evaluator.withgoogle.com): audits your CSP policy specifically

You can also check headers from the command line:

curl -I https://yourdomain.com

Common Mistakes to Avoid

  • Forgetting the always parameter: without it, headers are not sent on 4xx and 5xx error responses
  • Duplicate headers: setting the same header at http {}, server {}, and location {} levels causes Nginx to drop the parent-level ones
  • Enabling HSTS preload too early: getting removed from the preload list takes months
  • CSP with unsafe-inline: this defeats most of CSP’s value. Use nonces or hashes instead
  • Not testing in staging first: a strict CSP can break your site instantly
nginx server security

Quick Reference Table

Header Attack Mitigated Recommended Value
Strict-Transport-Security SSL stripping max-age=63072000; includeSubDomains; preload
X-Frame-Options Clickjacking SAMEORIGIN
X-Content-Type-Options MIME sniffing nosniff
Referrer-Policy URL leakage strict-origin-when-cross-origin
Permissions-Policy API abuse geolocation=(), camera=(), microphone=()
Content-Security-Policy XSS, injection default-src ‘self’

FAQ

Do I need all these security headers in Nginx?

At minimum, deploy HSTS, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy. These four have almost zero risk of breaking your site and provide major protection. CSP requires more careful tuning per application.

Will security headers slow down my Nginx server?

No. Headers add a few hundred bytes to each response and are processed in microseconds. The performance impact is negligible compared to the security gain.

How do I add security headers in Nginx Proxy Manager?

In NPM, edit your proxy host, go to the Advanced tab, and paste the add_header directives directly into the custom Nginx configuration field. Save and the changes apply on reload.

Why are my headers not showing up on error pages?

You forgot the always parameter. Without it, Nginx only sends add_header directives on 200, 201, 204, 206, 301, 302, 303, 304, 307, and 308 responses. Add always to apply them universally.

Can I use the same security headers config for multiple sites?

Yes, that is the recommended approach. Save your headers in /etc/nginx/snippets/security-headers.conf and include it in every server {} block. Centralizing the config makes updates trivial.

How often should I review my Nginx security headers?

Re-scan your sites every quarter. New browsers introduce new headers (like Cross-Origin policies) and deprecate old ones (X-XSS-Protection is now obsolete). Keep your config modern.

Final Thoughts

Configuring Nginx security headers is one of the highest ROI security tasks you can perform. Ten minutes of work prevents entire categories of attacks. Deploy the config block above, run a scan on securityheaders.com, and aim for that A+ grade. Then iterate on your CSP until you have a tight, app-specific policy you can trust.