Back
Vulnerabilities & CVEs13 Aug 2026 · 4 min read

Security headers: 54% of the top websites still get an F

by detektd team

Padlocks and chains on a blue metal gate
Photo: Michael Bennett · CC0 · StockSnap

Since 2015, researcher Scott Helme (creator of securityheaders.com and Report URI) has regularly crawled the million most visited sites to record the HTTP security headers they return. His June 2026 edition, published on June 29, is based on the Tranco list: 819,002 sites answered the June 13 crawl. It's the only series that lets you compare the state of web configuration over ten years with the same method.

0,0%

of the 819,002 top 1 million sites score an F on their security headers (Scott Helme, June 2026)

Ten years of real progress

Every curve goes up. In August 2015, 62,043 sites redirected to HTTPS; in June 2026 it's 658,038, or 80.3% of responding sites. HSTS, which forbids the browser from falling back to HTTP, grows from 11,308 to 252,846 sites (30.9%). Content-Security-Policy, the header that limits what a page may load and execute, goes from 1,365 sites to 170,057: 125 times more in ten years, and more than double since June 2022 (79,549).

Newer headers follow: Referrer-Policy is present on 229,130 sites (+223% since 2022), Permissions-Policy on 101,364 (+209%). The number of A+ sites has almost quadrupled since 2022 (from 2,860 to 10,496), and F sites dropped from 564,740 to 440,832. But that F is still the grade of more than one site in two.

A CSP that's present isn't a CSP that works

The report's most instructive figure is about policy quality. Of the 170,057 CSPs recorded, 79,464 (46.8%) contain 'unsafe-inline', and 71,094 (41.9%) 'unsafe-eval'. Applied to scripts, 'unsafe-inline' allows precisely any script written into the page, which is exactly the shape an XSS injection takes. Such a policy limits scripts loaded from unknown domains, but doesn't block the most common case.

0,0%

of CSPs across the top 1 million contain 'unsafe-inline', the directive that, applied to scripts, lets XSS through (Scott Helme, June 2026)

The approach recommended by Google and the W3C, a "strict" nonce or hash-based CSP with 'strict-dynamic', remains marginal: 24.7% of policies use a nonce, but only 2,774 (1.6%) add 'strict-dynamic', and 318 (0.2%) enable Trusted Types. Only 4.8% of policies declare a violation collection endpoint (report-uri or report-to): the vast majority of teams therefore wouldn't know whether their CSP is blocking an attack, or breaking a feature.

A CSP that doesn't protect, then a strict CSP
# Present, but any injected <script> tag still runs
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:

# Strict: only a script carrying this response's nonce runs
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; report-to csp

The nonce must be random and change on every response: a nonce frozen in a template or cached by a CDN is equivalent to 'unsafe-inline'. With 'strict-dynamic', scripts loaded by a trusted script inherit that trust, which avoids maintaining a list of domains. Next.js documents this setup through a middleware that generates the nonce per request.

HSTS, cookies, clickjacking: the details that matter

Same story for HSTS: of the 252,846 sites sending it, 69.2% set a max-age of at least one year, 49.8% cover subdomains, and only 53,019 (21%) meet all three conditions for the browsers' preload list. Without preloading, the very first visit still happens over HTTP, and can therefore be intercepted on a hostile network.

On cookies, 314,878 sites set at least one. Among them, 189,528 use the Secure attribute, 223,384 HttpOnly and 176,300 SameSite. In other words, 91,494 sites never use HttpOnly on any of their cookies, and 125,350 never use Secure. The __Host- and __Secure- prefixes, which guarantee to the browser that a cookie couldn't have been overwritten by a subdomain or an HTTP page, appear on only 802 and 1,913 sites.

Against clickjacking, X-Frame-Options still dominates (327,918 sites, 40%) while the CSP frame-ancestors directive, which supersedes it and accepts several origins, appears in only 77,873 policies. Conversely, 163,114 sites still send X-XSS-Protection, a header no major browser applies anymore. And some headers say too much: X-Powered-By announces Next.js on 17,541 sites, and "PHP/7.4.33", a version without fixes since November 2022, on 9,264.

A baseline that holds up

nginx: baseline security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
# CSP set by the application (per-request nonce), with frame-ancestors 'self'
proxy_hide_header X-Powered-By;
server_tokens off;
  1. Redirect all HTTP to HTTPS, then send HSTS with a max-age of at least one year. Add includeSubDomains and preload only once every subdomain is served over HTTPS.
  2. Roll out a nonce-based CSP with 'strict-dynamic', first as Content-Security-Policy-Report-Only with a reporting endpoint, then in blocking mode once the reports are clean.
  3. Set Secure, HttpOnly and SameSite on every session cookie, and prefix it with __Host- where possible.
  4. Remove headers that advertise versions (X-Powered-By, Server) and those that no longer do anything (X-XSS-Protection).

What detektd checks

On every scan, detektd reads the headers the site actually serves and doesn't stop at their presence: permissive CSP ('unsafe-inline', 'unsafe-eval', wildcards), static nonce, Report-Only CSP, short HSTS max-age or ineffective preload, missing HTTPS redirect, frameable page (clickjacking), session cookies without Secure or HttpOnly, mixed content, overly open Referrer-Policy and Permissions-Policy, plus the TLS configuration and the certificate.

Security headers: 54% of the top websites still get an F | detektd