The 14 HTTP Security Headers (and Which Actually Matter)

Not all security headers are equal. Some prevent real attacks daily. Some are legacy relics browsers ignore. Here's an honest priority list.

The laziest kind of security advice is the giant checklist where every HTTP header gets one bullet point and the same tone of voice.

Set these fourteen headers. Good luck. See you in production.

That is how you end up with sites proudly sending X-Content-Type-Options: nosniff while serving a JavaScript bundle with no Content Security Policy, no HSTS preload, and an iframe policy copied from a blog post in 2017. The headers are not equal. They do not solve equally serious problems. They do not cost the same to deploy. Treating them equally is like treating a fire extinguisher and a decorative plant as equivalent safety equipment because they’re both in the hallway.

After looking at thousands of header configurations across real domains, here’s how they actually stack up.

Tier 1: Do These First or Don’t Bother With the Rest

Strict-Transport-Security (HSTS). Forces the browser to use HTTPS for all future requests to your domain. Without it, the first request can be intercepted and downgraded to HTTP — even if your server redirects to HTTPS. This is one of the few headers where the absence creates a real, exploitable attack surface. Set max-age to at least a year. Include includeSubDomains if you control all subdomains. Consider preloading for mature domains.

Content-Security-Policy (CSP). Controls which scripts, styles, images, and other resources can load on your page. A properly configured CSP is the single most effective defense against XSS. It’s also the hardest header to get right, which is why I’m putting it in Tier 1 — not because it’s easy, but because it matters that much.

The problem: most real-world CSPs are broken. They use unsafe-inline and unsafe-eval because the application breaks without them, which defeats the entire purpose. A CSP with unsafe-inline is like a seatbelt with the clasp removed. Getting CSP right means confronting inline scripts, legacy templates, third-party tag soup, and the fact that somebody on the frontend was treating unsafe-inline as a personality trait. Nonces are usually the cleanest path. Hashes work for stable inline blocks.

CSP is not hard because the syntax is hard. CSP is hard because it reveals the shape of your app. That’s also why it matters more than half the header lists people obsess over.

X-Content-Type-Options: nosniff. One line. Prevents browsers from MIME-type sniffing responses. Without it, a browser might interpret a text file as JavaScript and execute it. The easiest security header to implement — one value, no configuration, no risk of breaking anything. Set it and move on.

Tier 2: Important, Worth Your Time

X-Frame-Options (or CSP frame-ancestors). Prevents your site from being embedded in an iframe on another domain. Stops clickjacking. DENY or SAMEORIGIN works. If you already have CSP, frame-ancestors 'self' does the same thing more flexibly. You don’t need both.

Referrer-Policy. The header people forget because it doesn’t sound dramatic. Then a password reset link or account path leaks to a third-party analytics endpoint through the Referer header and everyone suddenly remembers the browser likes to overshare by default. Set to strict-origin-when-cross-origin.

Permissions-Policy (formerly Feature-Policy). Controls which browser features your site can use: camera, microphone, geolocation. If your site doesn’t use the camera, explicitly disabling it means even XSS can’t activate it. Shrinks your attack surface for free.

Tier 3: Nice to Have, Situational

Cross-Origin-Opener-Policy (COOP), Cross-Origin-Resource-Policy (CORP), Cross-Origin-Embedder-Policy (COEP). The trio people copy without understanding and then blame when embeds break.

These headers help isolate browsing contexts and enable cross-origin isolation features. They matter for high-security web applications that need protection against Spectre-type timing attacks. For a marketing site or blog, they’re overkill. A scanner sees them missing and marks them red, so teams rush to add them before they’ve even deployed a meaningful CSP. That is backwards.

Tier 4: Legacy and Fossils

X-XSS-Protection. The classic zombie header. Modern browsers have removed their built-in XSS filters entirely — Chrome killed its XSS Auditor in 2019. Setting this header does nothing in any current browser. Some scanners still flag its absence, which is mildly infuriating. The recommended value is now 0 (off).

X-Permitted-Cross-Domain-Policies. Controls how Adobe Flash handles cross-domain data. Flash is dead. If that sentence sounds like a museum tour, good.

Cache-Control (security context). Not a security header per se, but sensitive pages — account details, billing, one-time tokens — should set no-store. Sometimes more valuable than a half-dozen obscure headers nobody on your team can explain.

The Real-World Gap

After looking at enough production configurations, a pattern appears. Teams set the easy headers because they feel like configuration. They skip the hard headers because they feel like engineering.

nosniff is easy. Referrer-Policy is easy. CSP is hard because it exposes templating habits, inline scripts, analytics sprawl, and deployment discipline. HSTS preload demands confidence about subdomains. So the median site collects small, tidy wins and postpones the one header that actually forces architectural honesty.

If you only remember one thing: a site with perfect HSTS and CSP but no X-Permitted-Cross-Domain-Policies is in far better shape than a site with every legacy header set but a broken CSP. A neat scanner score is nice. A header that changes attack surface is better.

Continue the conversation

← Back to Blog