# Legacy security headers you can retire (/en/blog/legacy-security-headers-to-retire)



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](/en/docs/web-security/policies/legacy-headers).

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

The rest of this post takes each row in turn.

## X-Frame-Options, replace with frame-ancestors [#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`](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors)
directive is the modern replacement, it supports multiple origins, wildcards,
and `'none'`.

```http
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](/en/blog/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](/en/blog/frame-ancestors-without-csp-header).

## X-XSS-Protection, drop it [#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:

```http
X-XSS-Protection: 0
```

Real XSS protection comes from a CSP that blocks inline scripts: see
[removing unsafe-inline](/en/blog/unsafe-inline-csp) and using nonces, hashes, and
Trusted Types.

## Feature-Policy, replace with Permissions-Policy [#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](/en/blog/permissions-policy-explained)). Migrate the directives across; the feature names are largely the same, the
syntax is not.

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

## report-uri, move to report-to [#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](/en/blog/report-uri-vs-report-to).

## Expect-CT and Public-Key-Pins, delete them [#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](/en/docs/web-security/security-headers/deprecated-headers).

## Headers that are not legacy, keep these [#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 [#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](/tools/security-headers) gives you that
before-and-after list against the table above, and the guide on how to
[improve your security headers grade](/en/blog/improve-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](/en/docs/web-security/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 [#next-steps]

* Replace clickjacking protection: [frame-ancestors](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors).
* Replace Feature-Policy: [Permissions-Policy](/en/docs/web-security/policies/permissions-policy).
* Build the CSP that replaces X-XSS-Protection: [how to build a strong CSP](/en/blog/how-to-build-a-strong-csp).

[Scan your security headers](/register).

## Sources [#sources]

* [W3C, CSP Level 3 - frame-ancestors](https://www.w3.org/TR/CSP3/#directive-frame-ancestors)
* [W3C, Permissions Policy](https://www.w3.org/TR/permissions-policy-1/)
* [Chromium, intent to remove the XSS Auditor](https://groups.google.com/a/chromium.org/g/blink-dev/c/TuYw-EZhO9g)
* [MDN, X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-XSS-Protection)
* [MDN, X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options)
* [MDN, Expect-CT (deprecated)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Expect-CT)
* [RFC 7469, Public Key Pinning Extension for HTTP](https://www.rfc-editor.org/rfc/rfc7469)
* [OWASP, HTTP headers cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
