必须添加add_header vary "accept-encoding",否则cdn因仅用url+host生成缓存键,会将gzip与未压缩内容混存,导致老旧浏览器收到gzip二进制流而白屏;该头使cdn将accept-encoding值纳入缓存键,实现版本隔离。

加 add_header Vary "Accept-Encoding" 是防止 CDN 缓存错乱的关键操作,不是可选项,而是必须项。它直接决定 CDN 是否能区分压缩版和未压缩版资源,避免用户拿到乱码或空白页。
为什么 CDN 会缓存错版本?
CDN 默认只用 URL + Host 构建缓存键(cache key),完全忽略请求头。当一个支持 gzip 的浏览器先访问,Nginx 返回了 gzip 压缩的 JS 文件并带上 Content-Encoding: gzip,CDN 就把这串二进制内容缓存下来;紧接着一个老旧设备(比如某些微信内置浏览器)发来不带 Accept-Encoding 的请求,CDN 仍匹配到同一个缓存条目,直接吐出 gzip 内容——浏览器无法解压,页面白屏或报错。
Vary: Accept-Encoding 怎么起作用?
这个响应头告诉 CDN:“本资源是否压缩,取决于客户端的 Accept-Encoding 值”。CDN 收到后,会把 Accept-Encoding 的值(如 gzip、br、空)纳入缓存键计算。结果就是:
-
GET /main.js+Accept-Encoding: gzip→ 存为 A 缓存 -
GET /main.js+Accept-Encoding: br→ 存为 B 缓存 -
GET /main.js+ 无Accept-Encoding→ 存为 C 缓存
三者互不干扰,各自按需返回,彻底规避错用风险。
配置时容易踩的坑
光写 add_header Vary Accept-Encoding 不够,还要注意上下文和配套设置:
- 不能只写在
server块顶层:必须放在具体location块内(如匹配.js、.css、图片等静态路径),否则对 HTML 等动态内容也加了 Vary,反而拖慢缓存效率 - 和
gzip_vary on不要重复叠加:如果已启用gzip_vary on,它会在实际触发 gzip 压缩时自动加 Vary;但若你同时用了gzip_static on(直接读 .gz 文件),Nginx 不走压缩流程,就不会自动加 Vary——此时必须手动add_header - 搭配
Cache-Control: public, immutable更稳妥:public允许 CDN 缓存,immutable告诉浏览器“这文件带哈希,一年内绝不会变”,省去反复验证请求,提升复用率 - Brotli 场景下要额外补
types:比如.wasm.br文件,若types块里没声明application/wasm wasm;,Nginx 不识别后缀,不会加Content-Encoding: br,Vary 也就失去意义
怎么验证它生效了?
用 curl 模拟不同客户端请求,比对响应头:
-
curl -H "Accept-Encoding: gzip" -I https://yoursite.com/app.js | grep -E "(Vary|Content-Encoding)"→ 应见Vary: Accept-Encoding和Content-Encoding: gzip -
curl -H "Accept-Encoding: br" -I https://yoursite.com/app.js→ 若启用了 Brotli,应返回br;否则应返回未压缩体,且仍带Vary: Accept-Encoding - 两次请求的
Etag或Last-Modified应不同(说明是独立缓存条目)
如果所有请求都返回一样的 Vary 头、但 Content-Encoding 不一致,说明 CDN 正确识别并分开了缓存。











