Content Decoding Errors

Last reviewed on 2026-10-03

When a response says it is gzip, Brotli or zstd and the bytes disagree — how to find out which side is lying.

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:

  1. Double compression — the body was compressed twice but the header names one coding.
  2. Mislabelled body — the header names a coding that was never applied (or a different one).
  3. Truncated body — the stream ended early, so the decompressor reaches the end of input mid-block.
  4. Format mismatch — raw DEFLATE vs zlib vs gzip under the wrong name.
  5. 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

WhereMessageMost likely cause
Chrome / Edgenet::ERR_CONTENT_DECODING_FAILEDDouble compression or a mislabelled body
Firefox"Content Encoding Error — uses an invalid or unsupported form of compression"Same as above
Python requests / urllib3ContentDecodingError: Received response with content-encoding: gzip, but failed to decode it. Error -3 while decompressing data: incorrect header checkBody is not gzip (plain text, or compressed twice)
Node.js zlibError: incorrect header check / unexpected end of fileWrong format / truncated body
Gogzip: invalid header / unexpected EOFWrong format / truncated body
Java, OkHttpjava.util.zip.ZipException: Not in GZIP formatHeader says gzip, body is not; a duplicated Content-Encoding: gzip header is a known trigger
Brotli decodersBrotliError: truncated brotli stream, "invalid data for declared content encoding 'br'"Body cut short, or labelled br without being Brotli
curlcurl: (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.