The response sends obsolete headers, one of them harmful
What this check looks for
Some of these headers do nothing at all now. One of them switches on a browser feature that was removed because it could itself be used to attack the page.
Why it matters
Dead headers are worse than clutter: X-XSS-Protection reads like a protection you have, so it gets ticked off a checklist while providing nothing, and the browsers that did honour it had bugs that let it be turned against the page.
When the check passes, your report says: “Every header sent is one browsers still act on”.
What it costs your score
When this check fails it removes 3 points from your HTTP score, before the status, confidence and repeat multipliers are applied. HTTP carries a weight of 7 in the overall score.
It shares the http.hygiene family ceiling of 10 points: however many findings that family produces, together they cannot remove more than that from HTTP. One underlying problem showing up in several places is still one problem.
- Severity
- low
- Default confidence
- confirmed
- Status when triggered
- warn
- Deduction
- 3 points
- Family cap
- http.hygiene · 10
- Category
- HTTP
- Module
- Http headers
- Fix owned by
- user
- In the ruleset since
- 2026.09
How to fix it
Remove the obsolete headers and rely on the mechanisms that replaced them.
They provide nothing, and X-XSS-Protection's filter was itself an attack surface on the browsers that had it.
Remove
X-XSS-Protectionentirely. If a compliance checklist demands the header, sendX-XSS-Protection: 0instead — that value disables the removed filter rather than requesting it, and this scanner does not report it.Deploy a Content Security Policy; that is the actual replacement for what the auditor pretended to do.
Remove
X-UA-CompatibleandP3P— nothing current reads either.Replace
Pragma: no-cacheandExpires: 0with an explicitCache-Control:no-storefor sensitive responses,no-cachefor ones that must be revalidated.Remove them at the origin, not only at a proxy — a proxy that is bypassed serves them again.
How to confirm it worked
curl -sSI https://‹host›/ | grep -iE '^(x-xss-protection|x-ua-compatible|p3p|pragma|expires):' — expect nothing
Cache-Control: no-store
Content-Security-Policy: default-src 'self'A named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.
Remediation by platform
# Requires the headers-more module (ngx_headers_more):
more_clear_headers 'X-XSS-Protection' 'X-UA-Compatible' 'P3P' 'Pragma';
# For a header added by a proxied upstream, stock nginx can do this instead:
# proxy_hide_header X-XSS-Protection;<IfModule mod_headers.c>
Header always unset X-XSS-Protection
Header always unset X-UA-Compatible
Header always unset P3P
Header always unset Pragma
</IfModule>header {
-X-XSS-Protection
-X-UA-Compatible
-P3P
-Pragma
}Rules → Overview → Create rule → Response Header Transform Rule → Remove (path as at 2026-09).Stock nginx cannot remove an arbitrary response header:
add_headeronly adds, andproxy_hide_headerworks only for a header arriving from a proxied upstream. If neither applies, stop the application emitting it instead — which is the better fix in any case.Shown because the response identified Apache. Put it in the virtual host or server configuration rather than .htaccess: .htaccess is re-read on every request, is often disabled by AllowOverride None, and runs too late to influence a response the virtual host generates itself.
alwaysmatters here: without it the rule skips error responses, which are frequently the ones that leak the most.This applies only to responses that reach visitors through Cloudflare. An origin that is reachable directly — by IP, or through a DNS record that is not proxied — still serves the response measured here unchanged. Setting it at the origin as well covers both paths.
Technical detail
Obsolete headers on the response from ‹origin›: ‹headers›. ‹detail›
What each one is:
- **
X-XSS-Protection: 1/1; mode=block** — the header that enables the legacy XSS auditor. Every major browser removed that filter: Edge dropped it in 2018, Chrome removed the auditor in version 78, and Firefox never implemented it. It was removed because it was a vulnerability in its own right — the filter could be steered into suppressing legitimate script to expose content it was protecting, andmode=block's abort behaviour was demonstrated as a cross-site leak channel. A real Content Security Policy is the replacement. **Note what this rule does not report:**X-XSS-Protection: 0is *not* flagged, because that value explicitly disables the filter and is what OWASP recommends for the browsers that still have one. Removing the header entirely is equally fine today. - **
X-UA-Compatible** — instructed Internet Explorer which rendering engine to emulate. IE has been out of support since June 2022 and the header is ignored by everything current. - **
P3P** — a compact privacy policy for a W3C specification that was abandoned; only old IE ever consumed it, usually as a workaround to make third-party cookies work in frames. - **
Pragma: no-cache** used as a cache directive — RFC 9111 §5.4 deprecates it outright and describes it as a *request* header. Alongside aCache-Controlit is inert; without one it is not a reliable instruction to anything. - **
Expires: 0** used as a cache directive — an invalid date, which a cache treats as already expired. It happens to produce roughly the intended effect, which is why it survives, but it is a malformed value standing in forCache-Control: no-storeorno-cache.
None of these is urgent; all of them are one line to remove. Detected server: ‹detected server›.
Standards and references
Test this on your domain
Run the check that produces this finding, on its own, against any domain.
Other http headers checks
- The caching instructions contradict each other
- A text response does not declare its character encoding
- The declared Content-Encoding may not match the body
- A compressible response was sent uncompressed
- The response has a body but no Content-Type
- No Alt-Svc header advertises HTTP/3
- A response header leaks an internal address
- The response does not say how long it may be cached
- The response cannot be revalidated without downloading it again
- A compressed response does not vary on Accept-Encoding
- Vary: * makes the response uncacheable by any shared cache