
A request returns HTTP 200, but the browser reports ERR_CONTENT_DECODING_FAILED and refuses to load a page, stylesheet, or JavaScript file. Start by checking whether the response body actually matches the compression declared in its headers.
This guide covers websites where the origin, reverse proxy, or CDN compresses or decompresses responses. The commands are diagnostic examples: replace the domain, resource path, and origin address with systems you are authorized to inspect. These tests have not been run against your production website.
Why a 200 response can still fail to load
The request header Accept-Encoding tells the server which content codings the client accepts. The response header Content-Encoding describes the coding applied to the body. When the server returns Content-Encoding: gzip, the client attempts to decode the body as gzip.[1]
Useful hypotheses to investigate include:
A proxy has decompressed the body into plain text but left a gzip response header in place.
Compressed data was truncated during generation or delivery.
A cached encoding variant has been mixed up with headers from a different version.
Multiple compression layers and header-rewrite rules have left an inaccurate coding declaration.
These are investigation paths, not proof of a CDN fault. Applying more than one content coding is not automatically invalid: HTTP permits multiple codings to be declared in the order applied. The failure occurs when the actual processing and the declaration disagree.[1]
Compare identity, gzip, and automatic decoding with GET requests
Choose a small, stable text resource, such as a JavaScript file used by the affected page. Avoid large downloads or endpoints containing sensitive data. The examples below use Bash on Linux or macOS and require curl; the gzip validation step also requires the gzip utility.
URL='https://www.example.com/assets/app.js' # Request an unencoded representation and save headers and body curl --connect-timeout 10 --max-time 30 -sS \ -H 'Accept-Encoding: identity' \ -D identity.headers -o identity.body "$URL" # Advertise gzip without enabling curl's automatic content decompression curl --connect-timeout 10 --max-time 30 -sS \ -H 'Accept-Encoding: gzip' \ -D gzip.headers -o gzip.body "$URL" # Let curl request supported compression and automatically decode it curl --connect-timeout 10 --max-time 30 -sS --compressed \ -D decoded.headers -o decoded.body "$URL"
Check the exit status after each command and retain error output. These commands deliberately avoid -I: you need the GET body to assess compressed data integrity, and HEAD alone cannot provide it. They also avoid --raw, which preserves transfer encoding; HTTP/1.1 chunk boundaries could interfere with a later gzip file check.[3]
Inspect the response status and the Content-Encoding, Content-Length, Vary, and Age fields, along with any cache-status headers documented by your CDN. Header names and meanings vary between providers. After a representation is transformed, the body and its metadata must remain consistent. If Content-Length is present, do not interpret it as the decompressed file size.[1]
One curl detail can easily lead to a false diagnosis: --compressed decompresses the saved body but does not rewrite the saved response headers. A gzip declaration in decoded.headers does not mean that decoded.body is still a gzip file.[3]
Validate gzip data instead of judging its appearance
Run the following only when the final response in gzip.headers declares gzip and no additional content coding:
gzip -t gzip.body # After validation succeeds, decompress the file for inspection gzip -dc gzip.body > gzip-decoded.body
If validation fails, check whether the file is already readable text, has been truncated, or contains an extra undeclared encoding layer. Compressed data normally looks unreadable in a text editor; that appearance alone is not evidence of corruption.
If the server returns Content-Encoding: br, use a Brotli-capable decoder or a curl build that supports it. Check curl --version before testing. A missing local decoder is not a server-side compression failure.[3]
Do not require dynamic responses collected at different times to have identical hashes. Timestamps, tokens, and personalization can change the content. For a stable static resource, comparing the decoded content is more useful.
Compare the origin while preserving the hostname and TLS identity
If you are authorized to connect directly to the origin and the network permits it, use --resolve to map the hostname to the origin address while keeping the original hostname in the URL. The IP below is a documentation example and must be replaced:
curl --noproxy '*' \ --resolve www.example.com:443:203.0.113.10 \ --connect-timeout 10 --max-time 30 -sS --compressed \ -D origin.headers -o origin-decoded.body \ 'https://www.example.com/assets/app.js'
curl uses the specified address for that hostname and port while retaining the URL hostname for the HTTPS request.[3] This comparison can fail if the origin allows only CDN traffic, requires a client certificate, or presents an origin certificate that your client does not trust. Such a failure does not establish that origin compression is broken. Do not leave the origin publicly exposed for testing, and do not use -k to conceal certificate problems.
| Comparison result | Next area to investigate |
|---|---|
| Automatic decoding also fails at the origin | Application compression middleware, origin proxies, and precompressed static files |
| Origin works but the CDN path fails | Edge compression, response-header rewriting, and cached variants |
| Identity works but gzip fails | gzip data integrity and the gzip processing chain |
| Cache misses work but cache hits fail | Cached objects and encoding-variant handling |
Use these results to narrow the investigation, then confirm with logs and response samples.
Keep each layer's body and headers consistent
Document what the application, Nginx, and CDN each do: compress, decompress, rewrite headers, or cache responses. Inspect rules that manually add or remove Content-Encoding, and look for stale length metadata after a body transformation.
Nginx's gzip module can add Vary: Accept-Encoding to relevant responses with gzip_vary on;. The gzip_proxied directive uses the presence of the request's Via header and its configured conditions to determine compression behavior. The fact that a reverse proxy exists is not enough to predict the result.[4]
The Nginx gunzip module decompresses gzip responses for clients that do not support gzip. It is not built by default and is not a general-purpose Brotli decoder.[5] Confirm both module availability and the conditions under which it operates before depending on it.
For a temporary mitigation, you can set gzip off; in an existing Nginx location that clearly matches the affected path, then check whether the application or CDN still compresses the response. This disables gzip at that layer only; it does not guarantee an uncompressed response across the whole delivery chain.
Back up the relevant configuration, record its original values, and validate a limited change first. If you have the required operational privileges and your deployment uses this service-management setup, run:
sudo nginx -t && sudo systemctl reload nginx
Containers, orchestration platforms, and other service managers need their own validation and reload procedures. Restore the backup if validation fails rather than experimenting across the entire site.
Remove incorrect cache variants after fixing the configuration
When the response varies with Accept-Encoding, encoding variants need to be declared and managed correctly. HTTP caches use the request fields named by Vary to decide whether a stored response can be reused. Whether a CDN distinguishes raw header values, normalizes encodings, or transforms content at the edge depends on its implementation.[2] Do not add every raw header combination to the cache key without checking the platform's behavior.
Use this recovery sequence:
Fix the body transformation and header rules, and verify that newly generated responses decode correctly.
Purge affected resources and confirm whether the purge covers all relevant encoding variants.
Test identity, gzip, and, where supported, Brotli requests for the same resource.
Repeat the requests to check that cache hits still decode. Record request times and cache status when comparing edge locations.
Restore any temporarily disabled compression and monitor browser and server errors.
Disabling the browser cache or adding random query parameters can help diagnosis, but neither replaces purging an incorrect cached object. Some cache policies ignore query parameters; random parameters can also change the cache path being tested.
Verify reliable decoding on the same resource path
Acceptance checks should establish that actual GET requests complete, compressed bodies decode fully, decoded content is correct, and both cache hits and misses behave consistently. Then verify that the page, CSS, and JavaScript load in the target browsers.
Permanently disabling compression across the site changes transfer costs and loading behavior. Once the fault is understood, restore a verified compression configuration and add header/body consistency checks to the release process.
Official references
Source verification date: October 9, 2026, carried over from the Chinese edition. These examples have not been executed in the user's production environment.
[1] IETF RFC 9110, HTTP Semantics: Sections 8.4, 8.6, 12.5.3, and 12.5.5. https://www.rfc-editor.org/rfc/rfc9110.html
[2] IETF RFC 9111, HTTP Caching: Section 4.1, cache matching with Vary. https://www.rfc-editor.org/rfc/rfc9111.html
[3] curl official manual: --compressed, --raw, --resolve, and --noproxy. https://curl.se/docs/manpage.html
[4] Nginx documentation: ngx_http_gzip_module. https://nginx.org/en/docs/http/ngx_http_gzip_module.html
[5] Nginx documentation: ngx_http_gunzip_module. https://nginx.org/en/docs/http/ngx_http_gunzip_module.html