Security Headers That Actually Matter (and the Ones That Don't)
Headers are the easiest thing to over-grade
Every scanner loves headers, because they are trivial to check. Send a request, read the response, dock a point for each missing name. That is why a header audit is the noisiest, least useful part of most reports, and why a site can score an F on some badge checker while being genuinely hard to exploit.
Headers are worth doing. They are just worth ranking honestly first, because a wall of red header findings is where the one finding that actually matters goes to hide. Here is the signal, separated from the ritual.
The four that earn their place
HSTS (Strict-Transport-Security)
Buys you: no downgrade to HTTP, no session cookie stolen over a stray plaintext request. It tells the browser "only ever talk to me over HTTPS." Cheap, high value, hard to break once your site is fully on HTTPS.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniff
Buys you: the browser stops guessing content types, which shuts down a class of attacks where an uploaded "image" is served back and executed as script. One header, no downside, no reason not to.
X-Content-Type-Options: nosniffFrame protection (frame-ancestors)
Buys you: clickjacking defense. It stops your site being framed onto an attacker's page that tricks a logged-in user into clicking something. Use the CSP frame-ancestors directive, the modern replacement for X-Frame-Options.
Content-Security-Policy: frame-ancestors 'self'CSP (Content-Security-Policy)
Buys you: the strongest in-browser mitigation for XSS, by restricting where scripts are allowed to load from. It is also the hardest to deploy without breaking your own app. Treat it as a project, not a header. Start in Content-Security-Policy-Report-Only mode, watch what breaks for a week, then enforce.
A header is a mitigation, not a patch. CSP reduces the blast radius of an XSS bug, it does not fix the bug. If your app is leaking secrets or your database is open, no response header saves you.
Why CSP is the one that takes real work
The other three are single lines you can ship today. CSP is different because it has to enumerate every legitimate source of script, style, image, and connection your app uses, and modern apps pull from a lot of places: analytics, a payment iframe, a font host, your own CDN. Get it wrong and you break your own site, which is why so many teams set a permissive script-src 'unsafe-inline' and call it done. That version passes a scanner and buys you almost nothing, because unsafe-inline is exactly what an XSS payload needs.
The version that actually helps uses a nonce or a hash so only your scripts run:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'self'Deploy it in report-only first, collect the violations your real users generate, widen the policy to cover the legitimate ones, and only then flip it to enforcing. That loop is the work. A CSP shipped in an afternoon is usually a CSP that does nothing.
The cargo cult
These show up in report after report, dressed as findings. Most of them are not.
| Header | Verdict |
|---|---|
X-XSS-Protection | Retire it. Deprecated, off by default in modern browsers, and its filter mode has itself caused bugs. 1; mode=block is pure ritual now. |
Expect-CT | Dead. Certificate Transparency is enforced by browsers directly; the header does nothing. |
X-Powered-By removal | Theater. Hiding your framework name stops no one. Fine to remove, just do not log it as a security win. |
Referrer-Policy | Privacy, not security. strict-origin-when-cross-origin is a sane default, but it is not stopping an attacker. |
Permissions-Policy | Niche. Worth it if you specifically want to lock down camera, mic, or geolocation. Low priority for most apps. |
How to prioritize
If you do nothing else this week, ship these three. They are cheap and almost never break anything:
Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Security-Policy: frame-ancestors 'self'Then treat CSP script-src as its own multi-week effort, because it is one.
And keep the ranking straight. A missing header is a hardening gap. A leaking .env is a breach, and no header configuration saves you from one, as we walk through in The .env File That Ends Companies. Do not let a page of red header findings crowd out the finding that ends your company, which is the whole argument in Why Your Scanner Lies.
Check yours in one command
curl -sI https://yourco.com | grep -iE 'strict-transport|content-security|content-type-options|frame'The missing lines are your to-do list, already in priority order.
Notra grades your headers, but it does not stop there or pretend a missing header is your biggest problem. It ranks them against the findings that can actually hurt you. Run a free scorecard, or read more on the blog.