Nginx configuration · Practical guide
How to configure security headers in Nginx
A header on the homepage does not prove that every Nginx route sends it. Start with the virtual server handling your public HTTPS hostname, check how its locations inherit headers, and verify the response after any application or CDN processing. Keep an existing working configuration and change one policy at a time.
Add a header to the existing HTTPS server
Suppose a page is missing X-Content-Type-Options. Add the directive below inside the existing HTTPS server block for that hostname. Keep its listen, certificate, routing and upstream settings: this fragment is not a replacement server configuration. Confirm scripts and styles already have appropriate Content-Type values before enabling nosniff.
The always parameter includes error responses such as 404; without it, add_header applies only to selected response statuses. It does not repair inheritance or affect a response generated entirely by a CDN. If this is the first add_header in the server block, also review any headers previously inherited from http.
# Inside the existing HTTPS server { ... } for your hostname
add_header X-Content-Type-Options "nosniff" always; - Check nginx -v and locate the active HTTPS virtual server and its included configuration files.
- Add the directive in staging, preserving existing policies and checking the location rules below.
- Validate and reload the configuration, then compare a normal page and a known 404 response.
Reference: Nginx: add_header and always
Check every location that adds its own headers
Under the default inheritance rules, a location inherits its parent add_header directives only when it declares none of its own. A location adding Cache-Control can therefore lose the security header from server. Inspect static-file locations, PHP handlers and error-page destinations, not just location /. The same issue applies between http and server.
A compatible approach is to keep the intended shared headers in an include file and include it in each block that defines its own add_header directives. Preserve route-specific policies and avoid including the same directives twice in one block. Retest the actual URLs after internal redirects or error handling.
Nginx 1.29.3 introduced add_header_inherit merge. On a supporting version, it appends parent values to the current level and its setting is inherited unless changed below. It does not replace or deduplicate an identically named header. The default remains the traditional behavior; do not assume a package supports merge without checking its version.
Give each policy one owner
Compare application, Nginx and public responses when a header appears twice. add_header adds a field; it does not generally replace an upstream field with the same name. Prefer removing the duplicate at its source. Keep application-generated policies there when their values depend on the rendered page, such as a CSP nonce.
If Nginx deliberately owns a fixed policy, hide only the corresponding upstream field before adding the replacement. Use the directive for the upstream protocol: proxy_hide_header applies to HTTP proxying, while fastcgi_hide_header applies to FastCGI, including common PHP-FPM setups. proxy_set_header changes the request sent upstream and is not a browser-response fix.
A CDN can add another copy or serve an older response after the origin changes. Inspect its transformation and cache rules, then recheck the public URL. Hiding an upstream field in Nginx cannot remove a field added later at the edge.
# Choose only the line matching the existing upstream handler.
# First remove its duplicate Referrer-Policy at the source if possible.
# HTTP upstream using proxy_pass:
proxy_hide_header Referrer-Policy;
# Alternative: FastCGI upstream using fastcgi_pass:
fastcgi_hide_header Referrer-Policy;
# Add the chosen replacement at the intended response scope:
add_header Referrer-Policy "strict-origin-when-cross-origin" always; Reference: Nginx: proxy response-header controls
Choose CSP and HSTS separately
Do not copy an enforced CSP from another application. Inventory your scripts, styles, connections and embedding requirements, then test a candidate in Content-Security-Policy-Report-Only. Review browser violations before enforcement. A second permissive CSP does not cancel an existing restrictive policy. Keep nonce generation and matching HTML under coordinated application control.
For HSTS, first verify HTTPS availability, certificate renewal and HTTP-to-HTTPS redirects. Start with a short max-age on HTTPS responses and increase it after testing. Use a long lifetime only after those checks pass; add includeSubDomains only after checking every affected subdomain. Preloading is a separate commitment, not a default header bundle.
Reference: MDN: HSTS deployment and lifetime
Validate, reload and inspect real GET responses
Use the same Nginx binary and configuration as the running service. nginx -t checks syntax and referenced files; reload only after it succeeds, using the commands below or your hosting platform’s supported deployment mechanism. Check the service log afterward. A successful syntax check does not establish that the correct virtual server or policy was selected.
The curl command makes a GET and prints headers without the body. Use the exact final HTTPS URL, then repeat it for a known 404 and representative application and asset routes. Read the status as well as the headers: a login redirect or CDN challenge is a different response. curl -I would test HEAD instead.
Confirm each intended header appears with the correct value and no unintended duplicate. Test scripts, forms and embedded content in a browser, then rescan the public URL with HeaderScan. Keep authenticated checks separate: the scanner does not sign in or run JavaScript.
sudo nginx -t
# Run only after the configuration test succeeds:
sudo nginx -s reload
# Replace this with the exact final HTTPS URL:
curl -q -sS --max-time 30 -D - -o /dev/null \
'https://example.com/'
Keep reading
Related guides
HTTP security headers: what to check first
Fix security header findings with CSP examples, a staged HSTS rollout, framing rules and practical browser and curl checks.
Read guide CachingCache-Control explained: no-cache, no-store and private
Choose Cache-Control for account pages, public HTML and versioned assets, then verify ETag revalidation and CDN behaviour with practical checks.
Read guide CookiesSecure, HttpOnly and SameSite cookies explained
Fix SameSite and session-cookie findings with Set-Cookie examples, host-only scope and browser checks for login, embedded content and logout.
Read guide LaravelHow to add security headers in Laravel
Add response headers with Laravel middleware, register them correctly, test normal and error responses, and check proxy or CDN overrides.
Read guide CSP implementationHow to roll out Content Security Policy with Report-Only
Build and test a CSP candidate, connect matching nonces and reporting endpoints, then enforce it with a practical rollback plan.
Read guideHTTP response headers reference: security, cookies and caching
Look up common HTTP response headers, their purpose and the checks to make before changing a policy.
Open reference