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%.
For this report we read 6 security headers from the final homepage response of every audited site, after redirects. A header sent only on a pre-redirect hop does not count, because the browser never applies it to the page. The question 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.
These figures count presence, not quality
Precision about the measurement matters here, because it sets the ceiling on what these numbers can claim.
The figures describe 1 response per site: the homepage, after redirects. Presence means a non-empty value, and nothing more. A Strict-Transport-Security header whose max-age is 1 second counts as present, and so does a Content-Security-Policy permissive enough to allow anything. Presence says nothing about whether 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 measurement 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.
Two details decide whether a header you already ship actually protects anything:
- Report-only CSP enforces nothing.
Content-Security-Policy-Report-Onlylogs violations without blocking them. It is the right way to start, but until the enforcingContent-Security-Policyheader ships, the policy protects nothing. frame-ancestorsand X-Frame-Options reach different clients. A CSPframe-ancestorsdirective is the better control, and older clients read only the legacyX-Frame-Optionsheader. Ship both.
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 older clients read only the legacy header.
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
Protective value and deployment risk disagree on exactly 1 header, and the fix order resolves the disagreement in favor of shipping.
Diagram source
graph TD
A[Read your own response headers] --> B[Deploy 1: HSTS with a short max-age]
B --> C[Deploy 2: nosniff, Referrer-Policy, Permissions-Policy, X-Frame-Options]
C --> D[Extend HSTS max-age once TLS is confirmed everywhere]
D --> E[CSP in report-only mode]
E --> F{Violation reports clean?}
F -->|No| G[Adjust sources, keep report-only]
G --> F
F -->|Yes| H[Enforce CSP, add frame-ancestors]Deploy 1 — HSTS, short. High protective value, 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 static lines together. nosniff, Referrer-Policy, Permissions-Policy and X-Frame-Options are static values with no application dependency. They are 4 of the 6 headers 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 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 the final response is the one the browser applies:
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 header your homepage does not send. All 6 coming back means the presence bar is cleared and the next question — whether the policies themselves are any good — is worth asking. A paid SEOReport diagnosis 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.
Get the complete diagnosis of your site
An evidence-backed report and a prioritized action plan, on a plan with monthly credits.