X-XSS-Protection header: deprecated, what to set (tested)

X-XSS-Protection turned on a filter in old browsers that tried to spot reflected XSS and block it. Current browsers no longer have that filter, so the header does nothing in them. We checked this by serving a page that echoes a query parameter into the HTML and sending every common value of the header. Short answer: send X-XSS-Protection: 0 or leave the header out, and use Content-Security-Policy to stop injected scripts.

1. The test: does the header stop a reflected script?

The test page put the q query parameter straight into the HTML with no escaping. That is a textbook reflected XSS bug. We loaded it with this payload in the URL:

?q=<script>window.__xss=1;document.title="PWNED"</script>

Then we checked whether window.__xss was set and whether the title changed. Each row is a different response header:

Response headerChromium 153Firefox 156
no X-XSS-Protectionscript ranscript ran
X-XSS-Protection: 0script ranscript ran
X-XSS-Protection: 1script ranscript ran
X-XSS-Protection: 1; mode=blockscript ranscript ran
X-XSS-Protection: 1; report=/rscript ran, no report sentnot tested
Content-Security-Policy: script-src 'self'blockedblocked

In Chromium the page rendered normally in every X-XSS-Protection case. mode=block did not blank the page. Chromium logged nothing about the header: no console message and no DevTools issue. The report= URL never received a request. The browser ignores the header completely.

With the CSP header, Chromium blocked the script and logged:

Executing inline script violates the following Content Security Policy directive 'script-src 'self''. Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked.

2. Why the header stopped working (from release notes, not measured)

Chrome removed its XSS Auditor in Chrome 78 (2019). Edge dropped its XSS filter in the move to Chromium, and Firefox never had one. Only old browsers such as Internet Explorer and pre-78 Chrome or Safari ever acted on the header. The filters were dropped partly because they missed many attacks and partly because attackers could abuse the blocking itself to break or probe pages. That second problem is why the current advice is 0 rather than 1; mode=block.

3. What popular libraries send today

LibraryX-XSS-Protection by default
helmet 8.3.0 (Express 5.2.1), app.use(helmet())0
helmet 8.3.0 with helmet({ xXssProtection: false })not sent
Django 6.1.1 SecurityMiddlewarenot sent
Django 6.1.1 with SECURE_BROWSER_XSS_FILTER = Truestill not sent, and no error

helmet sends 0 on purpose, to switch the filter off in any old browser that still has it. In the same response it also sent a Content-Security-Policy with script-src 'self', which is the part that actually blocks injected scripts.

Django 6.1.1 has no X-XSS-Protection code at all. SECURE_BROWSER_XSS_FILTER is gone from its default settings, and setting it to True is silently ignored. If an old Django settings file still has it, delete the line. It does nothing.

4. What to set

Either leave the header out or send:

X-XSS-Protection: 0

Both behave the same in current browsers. 0 is the safer choice if you still have users on old browsers. Do not send 1; mode=block. It protected nothing in either current browser we tested.

Server config (not run here):

# nginx
add_header X-XSS-Protection "0" always;

# Apache (mod_headers)
Header always set X-XSS-Protection "0"

If a proxy or older config already adds 1; mode=block, remove that line rather than adding a second header.

5. What actually stops XSS

Frequently asked questions

Is X-XSS-Protection deprecated?

Yes. No current major browser acts on it. In our test, Chromium 153 and Firefox 156 ran an injected script with X-XSS-Protection set to 0, 1, 1; mode=block, or not sent at all.

Should I set X-XSS-Protection to 0 or 1; mode=block?

Set 0 or leave it out. 1; mode=block did nothing in Chromium 153 or Firefox 156, and according to browser vendors the old filters it turned on could be abused. helmet 8.3.0 sends 0 by default.

Does Chrome still have the XSS Auditor?

No. According to Chrome's release notes it was removed in Chrome 78. In Chromium 153 the header made no difference and logged no console message.

What replaced X-XSS-Protection?

Content-Security-Policy. A script-src 'self' policy blocked the same injected inline script in both Chromium 153 and Firefox 156. Escaping output in your templates fixes the underlying bug.

Does Django still set X-XSS-Protection?

No. Django 6.1.1's SecurityMiddleware sent X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Cross-Origin-Opener-Policy, but no X-XSS-Protection, even with SECURE_BROWSER_XSS_FILTER = True in settings.

Written with AI assistance. Measured on 2026-10-02 with headless Chromium 153.0.8010.12 (Playwright 1.63.0) and headless Firefox 156.0.1 (geckodriver), loading a deliberately vulnerable page from a Node.js 24.21.0 server (Chromium test) and a Python 3.13 http.server (Firefox test) on localhost:8985. Defaults measured with helmet 8.3.0 on Express 5.2.1 and with Django 6.1.1 (SecurityMiddleware, test client). Browser history is quoted from release notes, not measured. The nginx and Apache lines were not run here. PlainHeaders is our extension; Chromium, Firefox, helmet and Django are free.