Compression Support by Browser
Chrome / Chromium
- ✓ Gzip: All versions
- ✓ Deflate: All versions
- ✓ Brotli: Version 50+ (2016)
- ◐ Zstandard: Behind flag (v123+)
Firefox
- ✓ Gzip: All versions
- ✓ Deflate: All versions
- ✓ Brotli: Version 44+ (2016)
- ✓ Zstandard: Version 126+ (2024)
Safari
- ✓ Gzip: All versions
- ✓ Deflate: All versions
- ✓ Brotli: Version 11+ (2017)
- ✗ Zstandard: Not supported
Edge
- ✓ Gzip: All versions
- ✓ Deflate: All versions
- ✓ Brotli: Version 15+ (2017)
- ◐ Zstandard: Behind flag (v123+)
Mobile Browser Support
iOS Safari
- Gzip and Deflate: All versions
- Brotli: iOS 11.0+ (2017)
- Zstandard: Not supported
Chrome for Android
- Gzip and Deflate: All versions
- Brotli: Version 50+ (2016)
- Zstandard: Behind flag (v123+)
Samsung Internet
- Gzip and Deflate: All versions
- Brotli: Version 5.0+ (2016)
- Zstandard: Not supported
How browsers actually negotiate
The compatibility table above is the surface. The mechanism behind it is straightforward: every browser sends an Accept-Encoding header on every request that can carry a body, listing the algorithms it understands. The server picks one and sends back a matching Content-Encoding response header so the browser knows how to decompress. The browser's role is purely declarative — it says what it accepts; it does not pick the algorithm.
That has practical consequences when you are debugging a "compression not working" report. The browser cannot turn compression on or off for a given response; it can only refuse to decompress something whose Content-Encoding it does not recognise. Almost every "Brotli is not working in Safari 14" issue turns out to be a server, CDN, or proxy that is not advertising or honouring br for that user-agent.
Why navigator does not expose Accept-Encoding
JavaScript running in the page cannot read Accept-Encoding. There is no navigator.acceptEncoding property — it is one of several "forbidden header names" the Fetch standard reserves for the browser. Even if you call fetch() with a custom Accept-Encoding, the browser will silently strip or override it. The reason is that the browser owns the decompression pipeline; user code cannot ask for an encoding the runtime cannot decode.
This means feature detection from the client side is essentially impossible. The reliable way to know what an individual visitor's browser supports is to read the Accept-Encoding header on the server.
Server-side detection (Node.js, Express)
app.get('/diagnostic', (req, res) => {
const accepted = (req.headers['accept-encoding'] || '')
.split(',')
.map(s => s.trim().split(';')[0]);
res.json({
userAgent: req.headers['user-agent'],
acceptEncoding: accepted,
supportsBrotli: accepted.includes('br'),
supportsZstd: accepted.includes('zstd'),
});
});
Quirks worth knowing
- HTTPS-only Brotli. Chrome and Firefox only advertise
bron secure origins. On a plain HTTP page, even a Brotli-capable browser sendsAccept-Encoding: gzip, deflate. This is the most common "Brotli is missing" surprise. - Range requests and Brotli. Some intermediaries strip
Content-Encoding: brfrom byte-range responses because the original cache entry was Brotli-encoded but the range was computed against the raw bytes. If your video player breaks after enabling Brotli, this is the suspect. - Old corporate proxies. A handful of MITM proxies still rewrite
Accept-Encoding: gzip, deflate, brdown toAccept-Encoding: gzipso they can inspect the response. Modern fleets have largely moved past this, but it remains visible in enterprise traffic. - Identity is implicit. Per RFC 9110, when a request omits
Accept-Encodingentirely, the server may serve any encoding or none. When the header is present but does not listidentity, the server may still send identity unless the client explicitly forbids it withidentity;q=0. - WebView differences. An app embedding a system WebView inherits the OS's HTTP stack, which may lag the standalone browser by months. iOS WKWebView and Android WebView track their respective system updates, not the App Store version of Chrome or Safari.
Picking algorithms based on the audience
For a typical public website with global traffic, advertising Brotli alongside gzip covers more than 95% of real visits and degrades gracefully for the rest. Adding Zstandard pays off only if you measure non-trivial Firefox 126+ traffic and your CDN supports it. Dropping deflate is safe; no current browser depends on it being available, and several intermediaries have historically mis-handled raw deflate (see the deflate guide for the zlib-vs-raw-deflate story).
If you operate an internal tool whose audience is narrower, the calculus changes. A B2B dashboard whose users are on managed Chrome installations can lean harder on Brotli and may benefit from precompressing static assets — see the precompression guide for how to ship pre-built .br files alongside your build output.
Legacy Browser Considerations
Supporting Older Browsers
- Always include gzip as a fallback - supported by all modern browsers
- Content negotiation - Let the server choose the best compression based on Accept-Encoding
- Progressive enhancement - Serve uncompressed content if no compression is supported
- Test thoroughly - Use tools like BrowserStack to test across different browsers
Implementation Notes
Best Practices for Cross-Browser Support
- Server Configuration: Configure your server to handle multiple compression algorithms
- Quality Settings: Use appropriate compression levels for different content types
- MIME Types: Ensure correct MIME types for compressed content
- Fallback Strategy: Always have a fallback to uncompressed content
- Testing: Test with real devices and browsers, not just developer tools
Verifying the header reaches your origin
If the diagnostic endpoint above returns the encodings you expect but real responses are still uncompressed, the next step is to check the path between the browser and your origin server. Common culprits, in roughly the order to suspect them:
- A CDN edge that strips or overrides the request header before it reaches the origin.
- A reverse proxy that forwards the request unchanged but compresses on the way back, then re-decompresses for HTTP/2 frames.
- A server framework that produces a streaming body and disables compression for chunked transfer.
- A response with
Cache-Control: no-transform, which tells intermediaries (and some servers) not to recompress.
The troubleshooting guide walks through each of these with the exact curl commands to use.
What to watch for next
Browser support for compression evolves slowly because changes affect every site on the web. Two threads are worth tracking:
- Wider Zstandard rollout. Firefox shipped
zstdas an advertised encoding in 2024; Chrome enabled it behind a flag in 2024 and has been moving toward default-on. Safari has not announced support at the time of this review. - Compression dictionaries. An IETF draft for shared dictionaries with Brotli and Zstandard would let a server reuse a small dictionary across many small responses, dramatically improving ratios on JSON and incremental HTML. If shipped, it changes the calculus on what to precompress.
Until those land, the practical recommendation is unchanged: serve Brotli and gzip from a single, correctly configured server stack and verify the response with a real browser, not just a synthetic test.