HTTP Security Headers: The Complete Implementation Guide
HTTP security headers are one of the most underutilized defenses in web security. This guide covers every critical header, what it does, and exactly how to implement it.
Why HTTP Security Headers Matter
HTTP security headers are instructions your server sends to browsers, telling them how to handle your content securely. They're your defense against some of the most common web attacks: XSS, clickjacking, MIME sniffing, and protocol downgrade attacks.
The best part? They're free, easy to implement, and require no code changes to your application — just web server configuration.
A website without security headers is like a bank with an unlocked door: it might never be robbed, but you've given attackers every opportunity.
The Critical Security Headers (Don't Skip These)
1. Strict-Transport-Security (HSTS)
Tells browsers to always connect over HTTPS, even if the user types http://.\u0060\u0060\u0060 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload \u0060\u0060\u0060
Parameters:
- \u0060max-age=31536000\u0060 — Remember this for 1 year (in seconds)
- \u0060includeSubDomains\u0060 — Apply to all subdomains
- \u0060preload\u0060 — Eligible for browser preload lists
Implementation in Nginx: \u0060\u0060\u0060nginx add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; \u0060\u0060\u0060
⚠ Warning: Start with a short max-age (300 seconds) until you're certain your entire site works over HTTPS. Once browsers cache HSTS, HTTP access is blocked for the duration.
2. Content-Security-Policy (CSP)
Specifies which sources are trusted for scripts, styles, images, and other resources. Prevents XSS by blocking unauthorized scripts.Strict CSP example: \u0060\u0060\u0060 Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; \u0060\u0060\u0060
For Google Analytics: \u0060\u0060\u0060 script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com; connect-src 'self' https://www.google-analytics.com; \u0060\u0060\u0060
Start in report-only mode to identify violations before blocking: \u0060\u0060\u0060 Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report \u0060\u0060\u0060
3. X-Frame-Options
Prevents your site from being embedded in iframes, blocking clickjacking attacks.\u0060\u0060\u0060 X-Frame-Options: DENY \u0060\u0060\u0060
Options:
- \u0060DENY\u0060 — No framing allowed anywhere
- \u0060SAMEORIGIN\u0060 — Framing allowed by same origin only
- \u0060ALLOW-FROM uri\u0060 — Deprecated, use CSP frame-ancestors instead
Note: CSP's \u0060frame-ancestors\u0060 directive supersedes this header in modern browsers. Implement both for maximum compatibility.
4. X-Content-Type-Options
Stops browsers from "sniffing" the content type of responses, preventing MIME confusion attacks.\u0060\u0060\u0060 X-Content-Type-Options: nosniff \u0060\u0060\u0060
Without this, a server returning a text file containing JavaScript could have that file executed as a script by the browser.
5. Referrer-Policy
Controls how much referrer information is sent with requests.\u0060\u0060\u0060 Referrer-Policy: strict-origin-when-cross-origin \u0060\u0060\u0060
Options from most to least privacy:
- \u0060no-referrer\u0060 — Never send referrer
- \u0060strict-origin\u0060 — Send only origin (no path) for cross-origin
- \u0060strict-origin-when-cross-origin\u0060 — Full URL same-origin, origin-only cross-origin (recommended)
- \u0060no-referrer-when-downgrade\u0060 — Don't send when going HTTPS → HTTP
- \u0060unsafe-url\u0060 — Always send full URL (bad for privacy)
6. Permissions-Policy (formerly Feature-Policy)
Controls which browser APIs and features can be used.\u0060\u0060\u0060 Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=() \u0060\u0060\u0060
The \u0060interest-cohort=()\u0060 directive opts out of Google's FLoC (now deprecated but still good practice).
Important But Optional Headers
Cross-Origin-Opener-Policy (COOP)
Isolates your page in its own browsing context group, preventing cross-origin windows from accessing your window object.\u0060\u0060\u0060 Cross-Origin-Opener-Policy: same-origin \u0060\u0060\u0060
Required if you want \u0060SharedArrayBuffer\u0060 access (for WebAssembly threads).
Cross-Origin-Embedder-Policy (COEP)
Prevents resources that don't explicitly grant permission from being embedded.\u0060\u0060\u0060 Cross-Origin-Embedder-Policy: require-corp \u0060\u0060\u0060
Cross-Origin-Resource-Policy (CORP)
Controls which origins can load your resources.\u0060\u0060\u0060 Cross-Origin-Resource-Policy: same-origin \u0060\u0060\u0060
Headers That Reveal Too Much
Some headers are dangerous because they expose server information that helps attackers:
X-Powered-By
Remove this immediately. It reveals your backend technology (PHP, Express.js, ASP.NET) and version, helping attackers find known vulnerabilities.\u0060\u0060\u0060nginx # Nginx: headers already not sent # Apache: Header unset X-Powered-By
# Express.js: app.disable('x-powered-by'); \u0060\u0060\u0060
Server
The Server header reveals your web server software and version. Minimize what you expose.\u0060\u0060\u0060nginx server_tokens off; # Hides nginx version \u0060\u0060\u0060
X-AspNet-Version / X-AspNetMvc-Version
Remove these in ASP.NET applications: \u0060\u0060\u0060xmlPerformance Headers
These aren't security headers but they significantly impact user experience:
Cache-Control
Controls how browsers and CDNs cache responses.\u0060\u0060\u0060 # For static assets (versioned with hash in filename): Cache-Control: public, max-age=31536000, immutable
# For HTML pages: Cache-Control: no-cache, no-store, must-revalidate
# For API responses: Cache-Control: private, max-age=0, must-revalidate \u0060\u0060\u0060
Content-Encoding
Enables compression. Make sure gzip or brotli is enabled:\u0060\u0060\u0060nginx gzip on; gzip_types text/plain text/css application/json application/javascript; brotli on; # Requires ngx_brotli module \u0060\u0060\u0060
Web Server Implementation
Nginx
\u0060\u0060\u0060nginx server { # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header Content-Security-Policy "default-src 'self'" always; # Remove info-leaking headers server_tokens off; # Performance gzip on; gzip_vary on; } \u0060\u0060\u0060Apache
\u0060\u0060\u0060apacheServerTokens Prod ServerSignature Off \u0060\u0060\u0060
Cloudflare (Transform Rules)
If you use Cloudflare, you can add headers via Transform Rules in the dashboard without touching your server config. This is particularly useful for HSTS and X-Frame-Options.Testing Your Security Headers
After implementing headers, verify them:
- Use our HTTP Header Checker to see all headers in real-time
- Check securityheaders.com for a letter-grade score
- Use browser DevTools → Network → click any request → Headers tab
- Test with \u0060curl -I https://yourdomain.com\u0060
Security Headers Score Card
| Header | Priority | Implementation Effort | Impact |
|---|---|---|---|
| HSTS | Critical | Low | High |
| X-Content-Type-Options | Critical | Very Low | Medium |
| X-Frame-Options | High | Very Low | High |
| Referrer-Policy | High | Very Low | Medium |
| CSP | High | High | Very High |
| Permissions-Policy | Medium | Low | Medium |