Why Security Headers Still Matter in 2026
Most website compromises don't start with a zero-day. They start with something boring: an injected script, a page loaded inside a malicious iframe, a browser guessing a file type it shouldn't. HTTP security headers are the cheapest defense you'll ever deploy — a few lines of server config that tell browsers exactly what your site is allowed to do.
The catch? Surveys of the top million sites consistently show that fewer than half send a Content-Security-Policy, and many that do ship one so permissive it's decorative. Let's fix that.
The Essential Headers, and What Each One Blocks
Every header below maps to a specific class of attack. If you remember nothing else, remember this table:
| Header | Blocks | Recommended value |
|---|---|---|
Content-Security-Policy | XSS, injected scripts, data exfiltration | Site-specific (see below) |
Strict-Transport-Security | HTTPS downgrade, SSL stripping | max-age=31536000; includeSubDomains; preload |
X-Content-Type-Options | MIME sniffing attacks | nosniff |
X-Frame-Options | Clickjacking (legacy browsers) | DENY or SAMEORIGIN |
Referrer-Policy | URL leakage to third parties | strict-origin-when-cross-origin |
Permissions-Policy | Silent abuse of camera, mic, geolocation | camera=(), microphone=(), geolocation=() |
A few notes on the ones people get wrong:
Strict-Transport-Security (HSTS)
HSTS tells the browser: never talk to this site over plain HTTP again. Without it, a user typing yoursite.com makes one unencrypted request first — and on hostile Wi-Fi, that single request is enough for an SSL stripping attack. Start with a short max-age (say, 300) while testing, then raise it to a year. Only add preload once you're certain every subdomain serves HTTPS — preload is very hard to undo. And obviously, HSTS is pointless if your certificate is broken; verify yours with an SSL Checker first.
X-Frame-Options vs frame-ancestors
These two overlap, and that confuses people. X-Frame-Options: DENY is the legacy header that stops your page from being framed. Its modern replacement is the CSP directive frame-ancestors 'none', which is more flexible (it accepts a list of allowed origins). When both are present, browsers that support CSP ignore X-Frame-Options. Best practice in 2026: send both — frame-ancestors for modern browsers, X-Frame-Options as a fallback for older ones.
X-Content-Type-Options
One value, no options: nosniff. It stops browsers from "sniffing" content and executing a file as JavaScript because it looks like JavaScript, even when your server said it was plain text. It's a one-liner that closes an entire attack class. There is no reason not to send it.
Content-Security-Policy: The Deep Dive
CSP is the heavyweight. It's a whitelist of everything your page may load — scripts, styles, images, fonts, frames, connections. A solid starting point looks like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
Key directives to understand:
default-src— the fallback for everything you don't specify. Always set it to'self'or'none'.script-src— the most important one. This is your XSS shield.base-uriandform-action— often forgotten, they prevent attackers from redirecting form submissions or rewriting relative URLs.frame-ancestors— clickjacking protection, as covered above.
Nonces vs unsafe-inline
The single worst thing you can put in a CSP is script-src 'unsafe-inline'. It re-opens the exact door CSP exists to close: any script injected into your HTML runs. If you have inline scripts you can't move to files, use nonces instead:
<!-- Server generates a fresh random nonce per response -->
<script nonce="r4nd0mB4se64">
initApp();
</script>
Content-Security-Policy: script-src 'nonce-r4nd0mB4se64' 'strict-dynamic';
The nonce must be unpredictable and unique per request — an attacker who can guess it can bypass the policy. Pair it with 'strict-dynamic' and scripts loaded by your trusted scripts are allowed too, which makes modern bundlers and tag managers much less painful.
Report-Only: Roll Out Without Breaking Production
A strict CSP deployed blindly will break something — an analytics snippet, a payment iframe, a font you forgot. That's what Content-Security-Policy-Report-Only is for. Same syntax, but violations are reported, never enforced:
Content-Security-Policy-Report-Only:
default-src 'self';
report-uri https://yoursite.com/csp-reports;
Run report-only for a week or two, review the violations, whitelist what's legitimate, then flip to enforcing. Building the policy itself is much easier with a CSP Generator — pick your sources per directive and copy the result instead of hand-assembling directive strings.
Nginx and Apache Config Snippets
Nginx — inside your server block:
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; base-uri 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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=()" always;
The always flag matters: without it, Nginx skips your headers on error responses (404, 500), and those pages are just as attackable.
Apache — in your vhost or .htaccess, with mod_headers enabled:
Header always set Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; base-uri 'self'"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
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"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
How to Audit Your Current Headers
Before adding anything, know where you stand. Three quick steps:
- Scan your security posture. Run your domain through a Security Headers Checker. You'll get a header-by-header verdict: what's present, what's missing, and what's misconfigured.
- Inspect the raw response. An HTTP Headers Checker shows you every header your server actually sends — useful for catching duplicates (two CSPs = the intersection is enforced, which surprises people) or headers injected by a CDN you forgot about.
- Verify your TLS foundation. HSTS and secure cookies assume your HTTPS setup is healthy — check expiry, chain, and protocol with the SSL checker.
Re-run the scan after every deploy that touches your reverse proxy. Headers regress silently: a CDN migration or a new load balancer can wipe them out with no visible symptom.
Common Mistakes to Avoid
- Setting CSP but keeping
unsafe-inlineandunsafe-eval— you've built a fence with the gate open. - HSTS
preloadon day one — get everything on HTTPS first; preload removal takes months. - Headers only on the homepage — misconfigured routing often leaves API routes or error pages unprotected.
- Deprecated headers —
X-XSS-Protectionis obsolete and can even introduce vulnerabilities in old browsers; set it to0or drop it.
Check Your Site in 30 Seconds
You now know what each header does and what a good value looks like. The next step takes half a minute: paste your URL into the Security Headers Checker and see exactly which protections you're missing. Free, instant, no account required — then use the CSP Generator to build the policy that closes the gaps.