What every decoding error has in common
A client reads Content-Encoding, hands the body to the matching decompressor, and the decompressor rejects the bytes. The header and the body disagree. There are only a handful of ways that happens:
- Double compression — the body was compressed twice but the header names one coding.
- Mislabelled body — the header names a coding that was never applied (or a different one).
- Truncated body — the stream ended early, so the decompressor reaches the end of input mid-block.
- Format mismatch — raw DEFLATE vs zlib vs gzip under the wrong name.
- An intermediary changed one but not the other — a proxy decompressed the body but kept the header, or vice versa.
Error messages and what they usually mean
| Where | Message | Most likely cause |
|---|---|---|
| Chrome / Edge | net::ERR_CONTENT_DECODING_FAILED | Double compression or a mislabelled body |
| Firefox | "Content Encoding Error — uses an invalid or unsupported form of compression" | Same as above |
| Python requests / urllib3 | ContentDecodingError: Received response with content-encoding: gzip, but failed to decode it. Error -3 while decompressing data: incorrect header check | Body is not gzip (plain text, or compressed twice) |
| Node.js zlib | Error: incorrect header check / unexpected end of file | Wrong format / truncated body |
| Go | gzip: invalid header / unexpected EOF | Wrong format / truncated body |
| Java, OkHttp | java.util.zip.ZipException: Not in GZIP format | Header says gzip, body is not; a duplicated Content-Encoding: gzip header is a known trigger |
| Brotli decoders | BrotliError: truncated brotli stream, "invalid data for declared content encoding 'br'" | Body cut short, or labelled br without being Brotli |
| curl | curl: (61) Error while processing content unencoding / "Unrecognized content encoding type" | Corrupt body, or a coding your curl build does not support |
Step 1: look at the raw bytes
Fetch the response without letting the client decompress it, and look at the first bytes. The magic numbers tell you what you really received:
curl -s -D headers.txt -H "Accept-Encoding: gzip" https://example.com/page -o body.bin
grep -i content-encoding headers.txt
head -c 8 body.bin | xxd
# 1f 8b ... gzip
# 78 01 / 78 5e / 78 9c / 78 da zlib ("deflate")
# 28 b5 2f fd Zstandard frame
# 3c 21 / 3c 68 / 7b "<!" "<h" "{" -> plain HTML/JSON, NOT compressed
# Brotli has no magic number: test with brotli -d -c body.bin > /dev/null
Then check whether one round of decompression gives you readable content:
gzip -dc body.bin | head -c 200 # readable? good
gzip -dc body.bin | head -c 4 | xxd # starts with 1f 8b again? DOUBLE gzip
Cause: double compression
The application compresses its output and a layer in front of it (Nginx, IIS, a CDN, a Worker, a framework middleware) compresses it again, or a precompressed .gz file is served and then compressed on the fly. The header usually shows a single gzip, sometimes gzip, gzip or a duplicated header line.
Fix: pick one layer to compress. Nginx's gzip filter and most CDNs skip responses that already carry Content-Encoding, so double compression usually means the application compressed without setting the header, or set it in a way the proxy did not see. Turn off compression in the application when a reverse proxy handles it.
Cause: header without compression
A rewrite rule, custom header or middleware adds Content-Encoding to a body that was never compressed, or names the wrong coding. A notorious example is an IIS URL Rewrite outbound rule that sets Content-Encoding: br whenever the request accepts br, regardless of what IIS actually produced — clients then try to Brotli-decode gzip or plain HTML and fail. Never set Content-Encoding as a static header; let the component that compresses set it. For Brotli on IIS the correct approach is in the IIS Brotli guide.
Cause: truncated responses
Errors that mention an unexpected end, truncated stream or EOF mean the decompressor ran out of input. Typical causes are a Content-Length that counts the uncompressed size (so the client stops reading early or waits forever), a proxy timeout or buffer limit cutting the response, an upstream crash mid-stream, or a cache that stored a partial object. Compare Content-Length with the number of bytes received, and look at the proxy's error log for upstream timeouts.
Cause: an intermediary decompressed but kept the header
Some proxies, security appliances and debugging tools decompress responses to inspect them, then forward the plain body with the original Content-Encoding. The raw-bytes check above shows plain text under a gzip header. Fix the intermediary's configuration, or ask it to request identity from the origin and compress on its own side.
Cause: deflate format confusion
Content-Encoding: deflate is defined as zlib-wrapped data, but some servers have sent raw DEFLATE under that name. Clients that accept only one form fail on the other. The cure is to stop sending deflate and use gzip, Brotli or Zstandard; see the deflate guide.
Cause: empty bodies with an encoding header
Responses to HEAD requests and 204 or 304 responses have no body. Most clients handle a Content-Encoding header on them correctly, but some older libraries try to decompress zero bytes and throw. If you control the server, avoid adding Content-Encoding to bodiless responses.
Working around it as a client
While the server is being fixed, you can usually avoid the error by asking for an uncompressed response: send Accept-Encoding: identity (see Accept-Encoding values). In Python, requests.get(url, headers={"Accept-Encoding": "identity"}); in curl, simply omit --compressed. If the server still sends a compressed body after that, it is ignoring Accept-Encoding entirely, which is itself a server bug.
For the broader "compression is not working" checklist, see the troubleshooting guide.
Frequently asked questions
What does ERR_CONTENT_DECODING_FAILED mean?
Chrome could not decompress the response body using the coding named in Content-Encoding. The body is usually compressed twice, not compressed at all, or truncated.
How do I fix "Received response with content-encoding: gzip, but failed to decode it"?
Download the raw body and check its first bytes. If it is plain text, the server sets Content-Encoding: gzip without compressing; if it starts with 1f 8b after one round of decompression, it is double-compressed. As a client-side workaround, send Accept-Encoding: identity.
What causes a "truncated brotli stream" error?
The Brotli decoder reached the end of the data before the stream was complete. Look for an incorrect Content-Length, a proxy timeout or buffer limit, or a body labelled br that is not Brotli at all.
Why does OkHttp throw "Not in GZIP format"?
The response declares gzip but the body is not gzip data. A server that sends the Content-Encoding: gzip header twice, or a body that is plain text, are common triggers.
How can I tell if a response is double-compressed?
Decompress it once with gzip -dc and inspect the first bytes. If they are still 1f 8b, the body was gzip-compressed twice.