The Security Headers Most Sites Still Skip: Data From 49 Real Audits
Response headers are single lines of configuration that ship with any deploy. Across the sites we audit, majorities send none of them — Permissions-Policy is absent on 69%, and the best-adopted header on the list is still missing on 41%.
Every audit we run reads the homepage's response headers. 6 of them carry a pass-or-fail verdict; report-only CSP and the cross-origin isolation pair are reported for context and never counted as failures. The read happens on the plain HTTP fetch — the same capture an AI crawler receives — and on the final response after redirects, so a site that sets its headers only on the pre-redirect hop gets no credit for them. The question the engine asks is deliberately narrow: did the response carry the header at all.
Across 49 sites audited between May 23 and August 31, 2026, the answer is usually no. Permissions-Policy is missing on 69% of them, Referrer-Policy on 65%, Content-Security-Policy on 61%, X-Frame-Options on 51%, X-Content-Type-Options on 43%, and Strict-Transport-Security — the best-adopted of the 6 — on 41%.
Methodology: latest completed audit snapshot per domain from our current checkset, roughly May 23 – August 31, 2026, 49 distinct domains, anonymized. Each percentage is the share of sites whose homepage response did not carry that header. The sample is self-selected — owners who ran an audit — and skews toward small and mid-size sites, which likely understates header adoption across the web as a whole, since large platforms set these at the edge. Treat the shares as directional for the long tail.
The check asks whether the header exists, not whether it is any good
Precision about the measurement matters here, because it sets the ceiling on what these numbers can claim.
The engine reads the homepage only — one URL, one response — and it takes the header values from the plain-HTTP capture when that capture is available, falling back to the other homepage capture when it is not. Presence means a non-empty value. There is no grading of directives: a Strict-Transport-Security whose max-age is 1 second passes, and so does a Content-Security-Policy permissive enough to allow anything. Nothing about a passing verdict says the policy is well-constructed.
That looks like a weak bar until you consider what it measures. A header with any value at all is evidence that a human once opened a server config, a CDN rule, or a middleware file and typed a directive. The check is a test of whether the configuration layer of a site has ever been touched on purpose — which is why the failure rates are the interesting part, not the pass rates.
Severity is assigned per header rather than uniformly. Strict-Transport-Security and Content-Security-Policy fail at high severity. X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy fail at medium. Cross-origin isolation, the COOP and COEP pair, is informational only — it passes when either header is present and otherwise reports an info verdict, never a failure, because those headers gate advanced browser capabilities rather than baseline defenses.
Two behaviors are worth knowing before you read your own report:
- Report-only CSP does not satisfy the CSP check. The engine looks specifically at
Content-Security-Policy. A site running onlyContent-Security-Policy-Report-Onlyfails the enforcement check and receives a separate informational verdict stating that violations are logged but not blocked. That verdict exists only when one of the two CSP headers is present. frame-ancestorsdoes not satisfy the X-Frame-Options check. The engine reads theX-Frame-Optionsheader itself. A modern CSP with aframe-ancestorsdirective is the better control, and a site that ships it while dropping the legacy header will still see that check fail.
There is also one platform accommodation in the code. When the response comes from Vercel and the page loads Vercel's BotID script, a missing CSP still fails, but at medium rather than high severity, with a note explaining that BotID needs inline-script allowances that fight strict CSP enforcement. A site can document the constraint deliberately by placing an HTML comment such as <!-- seoreport-ignore: vercel-botid-csp --> in the first 1 KB of the homepage; when the reason names CSP or BotID, the check drops to an informational verdict instead of a failure. This is the only escape hatch in the family, and it exists because a real platform constraint should not read as neglect.
Adoption tracks how old a header is, not how hard it is
The ordering of the failure rates is close to reverse chronological. HSTS, the oldest of the 6 and reducible to a toggle in every major CDN dashboard, is the best-adopted. X-Content-Type-Options and X-Frame-Options, both vendor conventions long predating their formal successors, sit in the middle. Referrer-Policy and Permissions-Policy, the two newest, are the two most-missing — and Permissions-Policy, which no common host sets on your behalf, is last.
None of them is harder than the others in engineering terms. 5 of the 6 are one static line in a server block, a CDN rule, or an edge middleware file, with no application changes and no behavioral risk. What separates them is how long they have had to accumulate defaults and how many blog posts, checklists, and framework templates have absorbed them. We saw the same shape when we ranked every failed check across our audit history: response headers dominated the top of the list, above canonicalization, rendering, and performance. That ranking covered a different window, a different sample, and an earlier checkset, so the two sets of percentages are not a trend line — but the position of headers at the top of both is the durable finding.
The other structural reason is invisibility. A missing header changes nothing on screen. No CMS warns you, no build fails, no dashboard turns red. The absence is only observable to something that inspects raw responses — which is exactly what an audit, a browser's security state, and an increasing share of automated readers do. It is the same asymmetry we found in render parity: what humans see in a browser and what machines receive over HTTP have quietly diverged.
What each header protects
Strict-Transport-Security tells the browser to refuse plaintext HTTP for your domain for a stated period, which closes the redirect window an attacker on the network can hijack before your http:// → https:// redirect fires. It is also the one header on the list that carries a commitment: browsers honor the max-age even if you later break HTTPS. Start short, confirm every subdomain and asset serves cleanly over TLS, then extend.
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy limits where scripts, styles, frames and connections may come from. It is the only header here that contains an incident rather than preventing a category of one: when a tag manager is compromised or user input reaches the DOM, CSP decides whether the injected script can execute or reach a server it chooses. It is also the only one that can break a working page, which is why it belongs at the end of the sequence below.
Content-Security-Policy-Report-Only: default-src 'self'
X-Frame-Options declares whether other origins may put your pages in a frame. Without it, your interface can be embedded under a transparent overlay on an attacker's page and clicked through by users who believe they are on your site. Modern CSP expresses this better with frame-ancestors; ship both, since the legacy header is what older clients and our own check read.
X-Frame-Options: DENY
X-Content-Type-Options stops browsers from guessing a response's type when the declared Content-Type looks wrong. Sniffing is how an uploaded file that you serve as text can be executed as a script instead. The header has exactly 1 valid value.
X-Content-Type-Options: nosniff
Referrer-Policy governs how much of the current URL travels to third parties on outbound clicks and subresource loads. Your URLs carry campaign parameters, internal search queries, account identifiers, and sometimes tokens, and every analytics pixel and font CDN on the page is a recipient. Major browsers have converged on a reasonable default, but a default is a decision the user agent makes for you and varies by client; sending the header makes your policy explicit, and lets a site handling sensitive URLs choose something stricter, such as same-origin.
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy decides which browser capabilities your document and its embedded frames may request — camera, microphone, geolocation, and the rest of the powerful-features list. Absent the header, everything you did not disable is available to every script on the page, including third-party tags you did not write. Deny what you do not use.
Permissions-Policy: camera=(), microphone=(), geolocation=()
Ship the 4 one-liners first, then spend an afternoon on CSP
Engine severity and deployment risk disagree on exactly 1 header, and the fix order resolves the disagreement in favor of shipping.
Deploy 1 — HSTS, short. Highest severity, and near-zero risk when your site already serves HTTPS everywhere. Set a modest max-age first so a TLS problem on a forgotten subdomain stays recoverable.
Deploy 2 — the 4 medium-severity lines together. nosniff, Referrer-Policy, Permissions-Policy and X-Frame-Options are static values with no application dependency. They are 4 of the 6 checks and, at these failure rates, the majority of what an average site is missing. There is no reason to stage them separately.
Then extend HSTS, once you have confirmed every host under the domain serves cleanly over TLS.
Last — CSP, in report-only mode. Deploy Content-Security-Policy-Report-Only, collect violations from real traffic, and enumerate the script and connection sources you actually depend on. Only then switch the header to enforcing and add frame-ancestors alongside your X-Frame-Options line. Highest severity, highest effort, last in sequence, because it is the only item on the list that can take a working page down.
Read your own headers in 1 command
The whole measurement is reproducible from a terminal. Follow redirects, because that is what the engine does:
curl -sSL -o /dev/null -D - https://example.com/ \| grep -iE 'strict-transport-security|content-security-policy|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'
Every line that does not come back is a check your homepage fails. All 6 coming back means the presence bar is cleared and the next question — whether the policies themselves are any good — is worth asking. Our free report runs the same read on every audit and lists each absent header with the value that resolves it.
The headers in this data set are not missing because they are difficult or contested. They are missing because nothing in the ordinary life of a website ever surfaces their absence: no error, no visual change, no failing build. The sites that pass are the ones where somebody, once, went looking — and that single deploy is still available to the majority that has not.
See How Your Site Ranks
Get a free AI-powered SEO report with actionable findings and priority fixes for your website.
No signup required.