Security headers, explained

A security header is a single line in an HTTP response that changes what the visitor’s browser is allowed to do with your page: run scripts only from named sources, never speak plain HTTP again, refuse to be framed, refuse to guess content types. None of them needs code, a library or a reboot — and yet most websites send few or none of them, which is why most websites grade F when a scanner checks. This article goes header by header through what each one actually stops, explains the F epidemic, and gives the fix order that recovers the most protection per hour of work. To see your own site’s verdict, run the Security Headers Check first and read along with your results open.

Headers are browser instructions, not server armour

Start with what a header is not. A security header does not patch your application, filter your traffic or authenticate anyone. It is a declaration the server attaches to a response, and the browser — the visitor’s browser, on the visitor’s machine — enforces it from that moment on. That sounds weak until you notice how many attacks are executed by the browser on the attacker’s behalf: running a script someone injected into a comment field, loading your login page inside an invisible frame on a hostile site, treating an uploaded text file as HTML because its contents looked script-like. Headers are how the server tells the browser, in advance, which of those moves are never legitimate on this site.

They are also defense in depth, and should be read that way. The canonical references for this space are the MDN HTTP documentation, which specifies each header’s syntax and semantics, and the OWASP Secure Headers Project, whose recommended values the testing guidance in the OWASP Web Security Testing Guide points to. Neither source promises that headers make a site safe. They promise something narrower and true: with the headers set, whole classes of browser-executed attack stop working even when an application bug gives the attacker a foothold.

What each header actually stops

Content-Security-Policy stops injected scripts from running. XSS and its cousins — a stored comment carrying a script, a skimming script injected through a compromised third-party tag — all need the browser to execute code the site never intended. A CSP is an allowlist: script-src names the origins scripts may come from, and everything else, including inline script and anything loaded from an attacker’s server, is refused. Its weak forms are famous: a policy containing 'unsafe-inline' lets every inline script run, including the injected one, which is why the checker treats such a policy as weak rather than good. CSP is the heaviest header to deploy and the one that stops the most.

Strict-Transport-Security stops the downgrade. The first time a visitor types your domain without a scheme, or follows an old http:// link, that request crosses the network in the clear, where it can be stripped, redirected or read — session cookie included. HSTS tells the browser: for the next max-age seconds, rewrite every http:// URL for this host to https:// before anything leaves the machine, and refuse certificate-error click-throughs. One honest limit: HSTS only protects visitors whose browsers have already seen the header once (the preload lists baked into browsers exist to close exactly that first-visit gap), and it is only ever honoured when it arrives over HTTPS itself.

Frame protection stops clickjacking. If your pages can be embedded in a frame, a hostile site can lay your real interface under a transparent layer of its own buttons and harvest the clicks — the victim sees a harmless-looking page and approves a transfer. The CSP directive frame-ancestors (and its older sibling, X-Frame-Options: DENY or SAMEORIGIN) names who may frame you; everyone else gets a blank frame. X-Content-Type-Options: nosniff stops a quieter trick: browsers historically second-guessed the declared Content-Type and executed files as scripts or HTML when the contents looked the part, turning an innocent upload into stored XSS. nosniff ends the guessing — the declared type is the type.

Referrer-Policy stops URL leakage. When a visitor clicks from your page to another site, the browser historically sent the full URL of your page as the Referer — including any password-reset tokens, search terms or record IDs sitting in it. A policy of strict-origin-when-cross-origin or stricter sends only the origin cross-site and nothing to downgrade-prone http targets. Permissions-Policy stops feature abuse: it declares which powerful browser capabilities — camera, microphone, geolocation, payment handlers — this site may use at all, so an injected frame or script cannot quietly request them in your name. And the bonus header, Cross-Origin-Opener-Policy, severs the window.opener link between your page and cross-origin popups, isolating your browsing context from pages that might probe it.

Why so many sites grade F

Because nobody’s default sends these headers. A stock web server, a stock framework scaffold, a stock static host: each ships responses with at most one or two of the six, and the two most valuable — CSP and Permissions-Policy — require decisions about the specific site, so no default can ship them safely. Responsibility then splits three ways: the framework team assumes the server adds headers, the server team assumes the framework does, and the CDN in front rewrites what it pleases anyway. The headers fall through the gap, silently, because nothing visibly breaks without them. An F-grade site usually looks and works exactly like an A-grade one; the difference only shows up on the day an attack needs the browser’s cooperation.

The published numbers match that story. One scanner’s published sample, reported in the weekly tool-scan roundup of 2026-11-04, found 65% of scanned sites grading F and 51% sending no Content-Security-Policy at all. Treat those as what they are — one scanner’s sample, published by a third party, not a measurement of the whole web and not our own data — but the direction is corroborated by every operator’s experience: headers are the rare security control whose absence is the statistical norm. Which is also the good news hidden in the statistic: the bar is on the floor, and climbing it is configuration, not engineering.

Fix order, by impact per hour

Work in the order the Security Headers Check makes obvious. First, the one-liners: X-Content-Type-Options: nosniff, frame protection, and a sane Referrer-Policy — thirty-five points of the hundred, a few minutes of work, and effectively zero breakage risk on a normal site. Second, HSTS — twenty more points — but only after you have verified that every page, subdomain and legacy redirect genuinely works over HTTPS, because a long max-age cannot be unsent. Third, Permissions-Policy: list the features your site actually uses, disable the rest; ten points for one careful line.

Last, and slowly, CSP. Its 35 points are the largest single block and the only fix that can take your own site down. Deploy it first as Content-Security-Policy-Report-Only with a reporting endpoint, read a week of violations, rewrite the inline scripts and event handlers it flags — or adopt nonces, as the MDN guidance recommends — and switch to enforcement only when the reports go quiet. Add Cross-Origin-Opener-Policy: same-origin whenever you do not rely on cross-origin popup flows; it is unscored in our table but cheap isolation. Then re-run the check and confirm the headers you added at the origin actually survived the CDN and proxies in front of it.

What headers cannot do

They cannot fix the hole, only shrink what an attacker can do through it. A CSP does not repair the comment field that failed to escape its input; it makes the injected payload refuse to run. HSTS does not repair a broken certificate chain — that is the SSL grader’s territory — and no header substitutes for patching, backups or access control. Read a perfect header grade as a well-locked door on a building whose other storeys still need their own checks, which is what the Website Health Check is for.

They also cannot be set-and-forgotten. A CSP written for last year’s site silently blocks this year’s payment widget — or, worse, gets loosened with * in a hurry and stops meaning anything. Treat the header set like the rest of your configuration: versioned, reviewed when the site changes, and re-checked after every platform move. The check takes ten seconds; run it whenever the stack in front of your pages changes, and the F epidemic stays somebody else’s statistic.

Sources and further reading

Frequently asked questions

My site graded F. Where do I start?

With the one-liners: nosniff, frame protection and Referrer-Policy recover 35 of the 100 points in minutes and break nothing. Then HSTS once HTTPS is verified everywhere, then Permissions-Policy, and CSP last, via Report-Only. The Security Headers Check gives you the exact Nginx or Apache line for each missing header, so you can work down your own result page in order.

Will a Content-Security-Policy break my site?

An enforced CSP can, if it forbids inline scripts or third-party origins your pages genuinely need — that is a policy doing its job against code nobody inventoried. The safe route is Report-Only first: the browser reports violations without blocking anything, you fix what the reports surface, and enforcement goes live only when the reports are quiet. Never silence breakage by adding 'unsafe-inline' or a wildcard; that keeps the header and loses the protection.

How often should I re-check my headers?

After every change to what sits in front of your pages — CDN settings, a new proxy, a framework upgrade, a hosting move — and as part of any periodic review. Headers live in configuration that other changes silently overwrite, and the failure mode is invisible: the site looks identical with and without them. A ten-second check after each deploy pipeline change is the cheapest insurance on this page.