All posts

Legacy security headers you can retire

CentralCSP Team ·

Last update:

Security scanners and rating agencies still flag old response headers, and teams still copy them from blog posts written a decade ago. The result is header sets that carry obsolete directives, sometimes ones that actively do nothing or that a newer policy already covers. This is a short audit: which legacy headers to retire, what replaces each, and the few old ones that are genuinely still worth sending. Full reference: legacy security headers.

Legacy headerVerdictReplacement
X-Frame-Options⚠️ Keep as fallbackCSP frame-ancestors
X-XSS-Protection❌ Retire (send 0)A CSP without 'unsafe-inline'
Feature-Policy❌ RetirePermissions-Policy
CSP report-uri⚠️ Keep as fallbackCSP report-to + Reporting-Endpoints
Expect-CT❌ RetireNothing, Certificate Transparency is now enforced by default
Public-Key-Pins❌ RetireNothing, HPKP was removed from browsers
X-Content-Type-Options✅ KeepNone, still current
Strict-Transport-Security✅ KeepNone, still current

The rest of this post takes each row in turn.

X-Frame-Options, replace with frame-ancestors

X-Frame-Options was the original clickjacking control: it said who could put your page in a frame. It has aged badly. The ALLOW-FROM value is obsolete, and modern browsers ignore the entire header when they encounter it, so a policy built on ALLOW-FROM protects nobody. The CSP frame-ancestors directive is the modern replacement, it supports multiple origins, wildcards, and 'none'.

Content-Security-Policy: frame-ancestors 'none'

When both are present, a supporting browser uses frame-ancestors and ignores X-Frame-Options, so you can keep X-Frame-Options: DENY or SAMEORIGIN as a fallback for very old browsers. But frame-ancestors is the one doing the work today, and X-Frame-Options vs frame-ancestors walks the two through side by side. If you cannot set CSP response headers at all, see clickjacking protection when you cannot set a CSP header.

X-XSS-Protection, drop it

X-XSS-Protection switched on the browser's built-in XSS auditor. That auditor turned out to be worse than nothing: it had bypasses, and attackers could abuse it to selectively disable legitimate scripts, so Chromium removed it entirely. The header now controls a feature that no longer exists in modern browsers. The common advice is to explicitly send 0 to make sure no residual legacy behavior kicks in:

X-XSS-Protection: 0

Real XSS protection comes from a CSP that blocks inline scripts: see removing unsafe-inline and using nonces, hashes, and Trusted Types.

Feature-Policy, replace with Permissions-Policy

Feature-Policy controlled access to browser features (camera, geolocation, and so on) but is deprecated. Permissions-Policy is its standardized successor, with a revised allowlist syntax and the ability to report violations through the Reporting API (Permissions-Policy explained). Migrate the directives across; the feature names are largely the same, the syntax is not.

Permissions-Policy: geolocation=(), camera=(self)

report-uri, move to report-to

The CSP report-uri directive still works but is deprecated in favor of the report-to directive plus the Reporting-Endpoints header. Because browsers that support report-to ignore report-uri, you can send both during the transition without double-reporting. The full migration is in report-uri vs report-to.

Expect-CT and Public-Key-Pins, delete them

Two headers are not deprecated so much as gone. Public-Key-Pins (HPKP) let a site pin the certificates browsers would accept for it, and a single mistake could lock a domain out of every browser for the pin's lifetime. It was removed from browsers rather than fixed. Expect-CT asked for Certificate Transparency enforcement, which browsers now apply by default, so the header has nothing left to ask for.

Neither has a replacement, because neither needs one. Delete them; anything still sending them is shipping bytes no browser reads. Both are covered in the deprecated headers reference.

Headers that are not legacy, keep these

A cleanup is also where people accidentally remove headers they should keep. Two older headers are not obsolete and have no reporting-based replacement: X-Content-Type-Options: nosniff, which stops MIME-type sniffing, and Strict-Transport-Security (HSTS), which enforces HTTPS. Both are still recommended. Do not drop them while retiring the others.

Find what your site actually sends

Before you change anything, see the current state. A scan tells you which legacy headers a live site still sends and which modern replacements are missing, so you retire and replace in one informed pass rather than guessing. CentralCSP's free security headers scanner gives you that before-and-after list against the table above, and the guide on how to improve your security headers grade takes it from there.

The replacements are also the reason retiring is safe to verify rather than assume: frame-ancestors, Permissions-Policy and report-to all report violations through the Reporting API, which the old headers never did. Point them at a CentralCSP endpoint before you delete the legacy header and you can see the modern control working on live traffic first.

Next steps

Scan your security headers.

Sources