đź“… Updated: June 10, 2026 (Originally published: February 21, 2026)
NGINX configuration should be simple. Instead, most tutorials lead you into the add_header pitfall where headers mysteriously disappear when you add a nested location block. The NGINX security headers module eliminates this problem entirely. It adds intelligent header management that manual configuration cannot match.
This guide covers the ngx_security_headers module in depth. You will learn every directive, every option, and the security implications of each choice.
Why Use This Module Instead of Manual Headers?
Before diving into configuration, understand why this module exists. Manual security header configuration in NGINX has three fundamental problems.
The add_header Inheritance Problem
The add_header directive does not inherit into nested contexts. If you define headers in a server block and then add any add_header in a location block, all your server-level headers vanish:
server {
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
location /api {
add_header X-API-Version "1.0";
# X-Frame-Options and X-Content-Type-Options are GONE here
}
}
This behavior has caused countless security misconfigurations. The module operates as a filter and adds headers regardless of location nesting.
Content-Type Awareness
Sending X-Frame-Options for a CSS file wastes bandwidth. It also violates the principle of minimal headers. The module automatically applies frame-related headers only to HTML content types. It does not add them to images, scripts, or stylesheets.
Conditional GET Handling
When a client sends If-Modified-Since and receives a 304 Not Modified response, there is no response body. The module intelligently skips headers that only matter for full responses. This reduces unnecessary header transmission.
Enable the NGINX security headers module with sensible defaults:
http {
security_headers on;
hide_server_tokens on;
server {
listen 443 ssl;
server_name example.com;
# ... your configuration
}
}
This single configuration adds the following headers to HTML responses:
Header
Value
X-Frame-Options
SAMEORIGIN
X-Content-Type-Options
nosniff
Referrer-Policy
strict-origin-when-cross-origin
Cross-Origin-Resource-Policy
same-site
Strict-Transport-Security
max-age=31536000; includeSubDomains; preload
It also removes the Server header and strips over 20 other headers that leak software information.
Configuration Directives Reference
security_headers
Syntax: security_headers on | off
Default: off
Context: http, server, location
This is the master switch. When enabled, the module adds the default set of security headers and enables all other security_headers_* directives.
http {
security_headers on;
server {
listen 80;
# Headers apply here
location /legacy {
security_headers off;
# Headers disabled for legacy endpoints
}
}
}
hide_server_tokens
Syntax: hide_server_tokens on | off
Default: off
Context: http, server, location
When enabled, this directive removes headers that reveal software information. It goes far beyond the built-in server_tokens off directive. That directive only affects the Server header version number.
The hide_server_tokens on directive removes these headers completely:
Header
Source
Server
NGINX itself
X-Powered-By
PHP, Node.js, etc.
X-CF-Powered-By
Cloudflare
Via
Proxy servers
X-Amz-CF-ID
AWS CloudFront
X-Amz-CF-Pop
AWS CloudFront
X-Page-Speed
mod_pagespeed
X-Varnish
Varnish Cache
X-Cache
Various caches
X-Cache-Hits
Various caches
X-Cache-Status
Various caches
X-Application-Version
Application frameworks
X-Hudson
Jenkins/Hudson
X-Jenkins
Jenkins
X-Envoy-Upstream-Service-Time
Envoy proxy
X-Drupal-Cache
Drupal CMS
X-Generator
CMS platforms
X-Backend-Server
Load balancers
Important: Some headers serve functional purposes. For example, X-Page-Speed prevents infinite loops when PageSpeed fetches resources. Enable hide_server_tokens only on front-facing NGINX instances that serve browsers directly.
# Front-facing NGINX serving browsers
server {
listen 443 ssl;
security_headers on;
hide_server_tokens on; # Safe here
}
security_headers_xss
Syntax: security_headers_xss off | on | block | omit
Default: off (sends X-XSS-Protection: 0)
Context: http, server, location
Controls the X-XSS-Protection header. This header is deprecated. The module sends 0 by default to disable it.
Value
Header Sent
Recommendation
off
X-XSS-Protection: 0
Recommended. Disables the broken XSS filter.
on
X-XSS-Protection: 1
Not recommended. Filter has known bypasses.
block
X-XSS-Protection: 1; mode=block
Not recommended. Can introduce vulnerabilities.
omit
(none)
Use if your upstream application sends this header.
Why the default sends 0: The XSS filter in older browsers is deprecated. It can introduce XSS vulnerabilities. Modern browsers have removed this feature entirely.
Controls the Referrer-Policy header. This header determines how much referrer information is sent with requests.
Policy
Behavior
no-referrer
Never send referrer
no-referrer-when-downgrade
Send full URL, except for HTTPS→HTTP
same-origin
Send referrer only to same origin
origin
Send only the origin (no path)
strict-origin
Send origin only, except for HTTPS→HTTP
origin-when-cross-origin
Full URL to same origin, origin only cross-origin
strict-origin-when-cross-origin
Default. Balanced privacy and functionality
unsafe-url
Always send full URL (not recommended)
omit
Do not send this header
security_headers_hsts_preload
Syntax: security_headers_hsts_preload on | off
Default: on
Context: http, server, location
Controls whether preload is included in the Strict-Transport-Security header.
With preload (default): max-age=31536000; includeSubDomains; preload
Without preload: max-age=31536000; includeSubDomainsCritical warning: When preload is included, your domain becomes eligible for browser preload lists. Browsers will refuse HTTP connections to your domain permanently. This is difficult to undo.
# Conservative - HSTS without preload
server {
listen 443 ssl;
security_headers on;
security_headers_hsts_preload off;
}
Important: The module only sends HSTS for HTTPS requests. HTTP requests never receive the HSTS header. This is correct behavior per RFC 6797.
Controls the Cross-Origin-Opener-Policy (COOP) header. This header manages window opener relationships across origins.
Why the default is omit: Enabling COOP can break popup communication patterns. Many applications rely on these for OAuth flows and payment windows.
Controls the Cross-Origin-Embedder-Policy (COEP) header. This header restricts embedding of cross-origin resources.
Why the default is omit: Enabling COEP breaks third-party resources without CORS headers. This includes analytics, CDN assets, and fonts.
The NGINX security headers module transforms security header management from error-prone manual configuration into reliable automated protection. With security_headers on; and hide_server_tokens on;, you get comprehensive security headers that work correctly across nested locations.
The module respects content types and handles conditional requests properly. For most websites, the default configuration provides excellent security. Use the directive-specific options only when your application requires different settings.
Install the module today and eliminate the add_header inheritance problem forever.
Continuous monitoring. Setting headers once is the easy part. Catching the day a refactor moves security_headers into a nested location that does not inherit it is where a scheduled scanner pays off. GetPageSpeed Amplify runs scheduled gixy scans across every host and ties findings to live NGINX runtime metrics. Drop-in compatible with the deprecated nginx-amplify-agent (EOL January 2026).
]]>