网站开启 CDN 压缩后出现 ERR_CONTENT_DECODING_FAILED?gzip、Brotli 与响应头排查
本内容发表于:2026-10-09 11:36:27
浏览量
1077

源站与 CDN 节点传输压缩数据,浏览器端出现编码不匹配,绿色通道展示修复后成功传输

页面返回了 200,浏览器却报 ERR_CONTENT_DECODING_FAILED,CSS 或 JavaScript 也加载不出来。遇到这种情况,可以从一项具体检查入手:响应头声明的压缩方式,是否与收到的正文一致。

本文适用于源站、反向代理和 CDN 之间存在压缩或解压处理的网站。下面的命令是排查示例,需替换为自己有权限检查的域名、资源路径和源站地址;本文没有在你的生产网站上执行这些测试。

200 状态码为什么仍然打不开

Accept-Encoding 是客户端告知服务器可接受哪些内容编码的请求头;Content-Encoding 描述当前正文实际经过的编码。服务器返回 Content-Encoding: gzip 时,客户端会按 gzip 解码正文。[1]

检查中值得关注的几种情况:

  • 响应头写着 gzip,正文已经被中间代理解压成普通文本。

  • 压缩数据在传输或生成时被截断,无法完整解码。

  • 缓存混用了不同编码版本,响应头与正文不再对应。

  • 多层压缩与中间层改头规则组合,留下了错误的编码声明。

这些是排查假设,不能只凭错误名称就判定 CDN 有故障。多层压缩也不必然违法:HTTP 允许按顺序声明多个编码;问题出在实际处理与声明不一致。[1]

用实际 GET 请求对比普通、gzip 和自动解码

先选一个较小、稳定的文本资源,例如故障页面使用的 JavaScript 文件。避免拿大文件或包含敏感信息的接口当测试对象。以下示例适用于 Linux/macOS 的 Bash 环境,需要 curl;gzip 校验另需 gzip 工具。

URL='https://www.example.com/assets/app.js'

# 请求未编码版本,保存响应头与正文
curl --connect-timeout 10 --max-time 30 -sS \
  -H 'Accept-Encoding: identity' \
  -D identity.headers -o identity.body "$URL"

# 仅声明接受 gzip,不开启 curl 自动内容解压
curl --connect-timeout 10 --max-time 30 -sS \
  -H 'Accept-Encoding: gzip' \
  -D gzip.headers -o gzip.body "$URL"

# 由 curl 请求其支持的压缩方式,并自动解码
curl --connect-timeout 10 --max-time 30 -sS --compressed \
  -D decoded.headers -o decoded.body "$URL"

每条命令执行后都要检查退出码,并保留错误输出。这里没有使用 -I:需要检查 GET 的正文,单看 HEAD 响应无法判断压缩内容是否完整。也没有使用 --raw,因为它会保留传输编码,HTTP/1.1 分块边界可能干扰后续 gzip 文件校验。[3]

查看响应头中的状态码,以及 Content-Encoding、Content-Length、Vary、Age 和厂商提供的缓存状态字段。厂商字段的名称和含义以对应文档为准。HTTP 在表示层转换后,需要保持正文和元数据一致;有 Content-Length 时,不能把它当作解压后文件大小。[1]

还要注意一个容易误判的细节:curl 的 --compressed 会解压保存的正文,但保存下来的响应头不会因此改写。decoded.headers 中仍有 gzip,并不意味着 decoded.body 还是 gzip 文件。[3]

校验 gzip 数据,不靠“看起来像乱码”判断

仅在 gzip.headers 确认最终响应声明 gzip,且没有其他叠加内容编码时,执行:

gzip -t gzip.body
# 校验通过后,再解压查看内容
gzip -dc gzip.body > gzip-decoded.body

出现校验错误时,继续判断正文是否已经是可读文本、是否被截断,或是否含有未声明的额外编码层。压缩文件在文本编辑器中显示乱码是正常现象,不能当作故障证据。

如果服务器返回 Content-Encoding: br,请使用支持 Brotli 的解码工具或支持该算法的 curl。先用 curl --version 检查构建能力,不要把本机缺少解码支持误判成服务器故障。[3]

对动态页面,不要求不同时间取得的正文哈希相同。时间戳、令牌和个性化内容都可能改变结果;静态资源则可对解码后的内容做对比。

绕过 CDN,保留域名和 TLS 身份做对照

在获授权且网络允许直连源站时,用 --resolve 将域名映射到源站地址,URL 仍保留原域名。以下 IP 是文档示例,必须替换:

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 会对指定主机和端口使用给定地址,同时保留 URL 主机名用于 HTTPS 请求。[3] 如果源站只允许 CDN 回源、要求客户端证书,或使用客户端不信任的源站证书,这个对照会失败;它不能据此证明源站压缩有问题。不要为了测试长期放开公网访问,也不要用 -k 掩盖证书问题。

对照结果接下来检查什么
源站自动解码也失败应用压缩中间件、源站代理、预压缩静态文件
源站正常,CDN 路径失败边缘压缩、响应头改写、缓存版本
普通版本正常,gzip 失败gzip 数据完整性及 gzip 相关处理链
缓存未命中正常,命中失败缓存对象及编码变体管理

表中的结果用于缩小范围,需要结合日志和响应样本确认。

修复要让每一层的正文与响应头对应

逐层记录应用、Nginx 和 CDN 的处理职责:谁压缩、谁解压、谁改写响应头、谁缓存。重点检查手工添加或删除 Content-Encoding 的规则,以及正文转换后遗留的长度信息。

Nginx 的 gzip 模块可以通过 gzip_vary on; 为相关响应加入 Vary: Accept-Encoding。gzip_proxied 则根据请求中的 Via 以及配置条件决定是否为被代理的请求压缩,不能仅凭“用了反向代理”就判断它的行为。[4]

Nginx 的 gunzip 模块用于向不支持 gzip 的客户端解压 gzip 响应;该模块并非默认编译启用,也不能作为通用 Brotli 解码器。[5] 如果处理链需要解压,必须确认模块能力及实际生效条件。

需要临时止损时,可以在一个已存在、明确匹配故障路径的 Nginx location 中临时设置 gzip off;,并检查应用和 CDN 是否还会压缩。它只关闭这一层的 gzip,不能保证整条链路变为未压缩响应。

改动前备份相关配置,记录原值,先小范围验证。拥有运维权限且部署方式适用时,可在修改后执行:

sudo nginx -t && sudo systemctl reload nginx

容器、编排平台或其他服务管理方式应使用各自的验证与重载流程。验证失败时恢复备份,不进行全站试错。

修配置后,还要处理错误的缓存版本

当响应随 Accept-Encoding 改变时,需要正确声明并管理编码变体。HTTP 缓存会根据 Vary 指定的请求字段判断能否复用响应;CDN 是否按原值区分、归一化编码,或在边缘转换,需要查具体平台实现。[2] 不建议未经核验就把原始请求头所有组合都加入缓存键。

推荐按以下顺序处理:

  1. 修正正文转换与响应头规则,确认新生成的响应可以解码。

  2. 清理已受影响的资源缓存,并确认清理操作是否覆盖所有相关编码变体。

  3. 对同一资源分别测试普通、gzip 和支持时的 Brotli 请求。

  4. 重复请求,检查缓存命中后是否仍能解码;跨节点观察时记录请求时间和缓存状态。

  5. 恢复临时关闭的压缩功能,继续观察浏览器与服务端错误。

关闭浏览器缓存或增加随机查询参数只能辅助诊断,不能代替清理错误对象。部分缓存策略会忽略查询参数,随机参数还可能改变被测试的缓存路径。

恢复标准是同一路径持续可解码

验收时至少确认:实际 GET 请求完成、压缩正文能完整解码、解码后的内容符合预期,以及缓存命中和未命中时结果一致。随后检查 CSS、JavaScript 和页面在目标浏览器中是否恢复。

永久关闭全站压缩会改变传输成本和加载表现。问题定位清楚后,应恢复经过验证的压缩配置,并把响应头与正文一致性检查纳入发布流程。

官方参考资料

资料核验日期:2026-10-09。本文示例未在用户生产环境执行。

[1] IETF RFC 9110,HTTP Semantics:第 8.4、8.6、12.5.3、12.5.5 节。https://www.rfc-editor.org/rfc/rfc9110.html

[2] IETF RFC 9111,HTTP Caching:第 4.1 节,Vary 与缓存匹配。https://www.rfc-editor.org/rfc/rfc9111.html

[3] curl 官方手册:--compressed、--raw、--resolve、--noproxy。https://curl.se/docs/manpage.html

[4] Nginx 官方文档:ngx_http_gzip_module。https://nginx.org/en/docs/http/ngx_http_gzip_module.html

[5] Nginx 官方文档:ngx_http_gunzip_module。https://nginx.org/en/docs/http/ngx_http_gunzip_module.html