Nginx Security Response Headers: A Few Lines That Block Common Attacks
Clickjacking, MIME-type sniffing, referrer leakage — a surprising number of frontend problems can be mitigated with HTTP response headers. A few add_header lines in Nginx shrink the attack surface significantly at almost zero cost. This article gives a copy-paste configuration and explains what each header does and the trade-offs involved. Security headers don't solve everything, but they are the cheapest, most stable layer of defense, and worth doing on sites of any size.
What these headers actually block
X-Frame-Options directly forbids third-party iframe embedding — the first line of defense against clickjacking. X-Content-Type-Options: nosniff forces browsers to honor Content-Type and kills a family of XSS variants. Referrer-Policy controls how much referrer leaks on outbound navigation; strict-origin-when-cross-origin keeps the analytics you need while avoiding leaking full URLs. Permissions-Policy turns off high-sensitivity APIs like geolocation, camera, and microphone by default, so even an injected third-party script cannot quietly call them.
The configuration
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:;" always;
Reference: OWASP Secure Headers Project https://owasp.org/www-project-secure-headers/
What each header does
| Header | Purpose | Recommended value |
|---|---|---|
| X-Frame-Options | Blocks embedding the site in an iframe (clickjacking) | SAMEORIGIN or DENY |
| X-Content-Type-Options | Stops browsers from MIME sniffing | nosniff |
| Referrer-Policy | Controls what referrer is sent on navigation | strict-origin-when-cross-origin |
| Permissions-Policy | Restricts browser API access (geolocation, camera, etc.) | Disable per need |
| Content-Security-Policy | Whitelists resource loading sources | Tuned to your site |
A real scenario: a promo page trapped in an ad iframe
A site launched a discount campaign page, and within days users reported that the page "was wrapped inside an ad and couldn't be clicked." Investigation showed a third-party script was loading the page inside its own iframe with an invisible overlay to trick clicks — textbook clickjacking. Adding X-Frame-Options: DENY made the page refuse to load in any iframe, and the problem vanished immediately. It also reminded the team that any page involving user clicks should refuse embedding by default, unless the business genuinely needs iframe sharing.
CSP: powerful but tricky
Content-Security-Policy is the most powerful response header — and the easiest to break things with. Putting default-src 'self' straight into production can block analytics, ads, and font scripts. A safer rollout path:
- Start in Report-Only mode — report without blocking for a week using
Content-Security-Policy-Report-Only. - Collect violation reports and add third-party domains to the whitelist one by one.
- Switch to enforced mode only after things look clean, keeping the reporting endpoint for ongoing monitoring.
This mirrors the "test first, enforce later" principle emphasized in the security headers guide. A practical CSP example: add the analytics domain to script-src, the font domain to font-src, and the image CDN to img-src, tightening one directive at a time.
Verifying with curl
curl -sI https://example.com | grep -iE 'x-frame|x-content|content-security|referrer'
Seeing the headers in the response confirms they are active. You can also score your site with an online checker such as SecurityHeaders.com and close the gaps one by one.
FAQ
Do these headers hurt SEO? No — search engines have no negative treatment for standard security headers; they actually reduce the risk of being impersonated by malicious sites. Will CSP block my ads and analytics? Yes, unless the corresponding domains are whitelisted — which is exactly why you start in Report-Only mode and open up gradually. Do I have to configure every site? Put the shared config in conf.d/security-headers.conf and include it globally; override per-site where needed. Which tools can I use to self-check? Beyond curl, SecurityHeaders.com, Mozilla Observatory, and Chrome DevTools' response-header panel all surface missing headers in one pass. Why does curl show different headers than the browser? Browsers run CSP reporting, cookie handling, and other logic that curl skips, so slightly different behavior is expected.
Working with CDNs and reverse proxies
When a CDN or reverse proxy sits in front of the site, security headers can be added at the outermost layer too: since the CDN caches response headers along with pages, configure headers centrally in the CDN console or at the edge instead of duplicating them per origin. Behind an Nginx proxy, proxy_hide_header strips unwanted headers from the origin and add_header appends security headers at the proxy layer. Watch the ordering — origin, proxy, and CDN can all add headers, and the outermost value wins or accumulates. Debug with curl and confirm which headers appear in the final response. If the origin sits behind multiple proxy hops, headers get forwarded layer by layer; keep the outermost layer as the backstop and the inner layers lean.
Notes
add_headeronly applies when the current scope produces a response. Add thealwayskeyword so security headers appear on 200, 404, 302, and every other response.- If your site loads third-party scripts (analytics, payments, support widgets), whitelist them in CSP before tightening, or features will break.
- Security headers are part of web security hardening; combined with WAF configuration and XSS defenses they work even better.
For the full header list and configuration templates, see the security headers configuration guide; for the complete security approach, browse the security hardening category.