nginx 实现 etag 协商缓存的核心是透传后端 etag 或对静态文件基于内容生成强绑定 etag,配合 cache-control 控制协商触发,并确保浏览器发起 if-none-match 请求后能正确返回 304;需禁用默认 mtime 依赖、避免头冲突、验证全流程闭环。

Nginx 本身不主动“验证”文件内容是否变更,它通过 ETag 实现的是协商缓存机制:当浏览器携带 If-None-Match 请求头发起条件请求时,Nginx 将该头透传给后端(或对静态文件自行比对),再根据响应结果决定返回 304 还是 200。关键在于让 ETag 真实反映内容变化,并与缓存策略协同生效。
确保 ETag 与文件内容强绑定
默认的 Nginx ETag 基于文件大小和修改时间(mtime),但构建部署中 mtime 易变,会导致内容未变而 ETag 变,引发无效重下载。应避免依赖默认行为:
- 前端资源(JS/CSS/图片)使用构建工具生成 contenthash 文件名(如
app.a1b2c3.js),并禁用 ETag:etag off; - 若需保留 ETag 协商能力,必须改用内容哈希——通过 CI/CD 在构建阶段计算文件 SHA-256,注入为响应头(如
X-Content-Hash: a1b2c3...),再在 Nginx 中映射:add_header ETag "$sent_http_x_content_hash"; - 禁用
Last-Modified(add_header Last-Modified "";),防止它与 ETag 冲突或因节点间 mtime 不一致导致校验失败
配合 Cache-Control 控制协商触发时机
ETag 是否被使用,取决于浏览器是否发起条件请求,而这由强缓存策略决定:
- 对带 hash 的静态资源,设长效强缓存:
add_header Cache-Control "public, max-age=31536000, immutable";—— 浏览器跳过验证直接复用,靠 URL 变更驱动更新 - 对不带 hash 的 HTML 入口文件,禁用强缓存,启用 ETag 协商:
add_header Cache-Control "no-cache";或max-age=0, must-revalidate,确保每次访问都校验最新版本 - 避免混用
no-cache和immutable,二者语义冲突,会导致协商逻辑失效
代理场景下透传并信任后端 ETag
当 Nginx 作为反向代理时,它不生成 ETag,只负责转发和复用:
- 确认后端接口(如
/api/data)稳定返回基于内容的 ETag(如ETag: "d41d8cd98f00b204e9800998ecf8427e") - Nginx location 中显式启用:
etag on;(对静态文件)或透传后端头:add_header ETag $upstream_http_etag;(对动态接口) - 禁用可能覆盖头的指令,如
add_header ETag ""或etag off;,防止破坏协商链路 - 确保缓存配置允许存储 ETag 头:
proxy_cache_valid 200 302 1h;,且不忽略关键头:proxy_ignore_headers Set-Cookie Vary;(如无用户态)
验证是否真正生效
不能只看响应头是否存在 ETag,要验证整个协商流程是否闭环:
- 首次请求:检查响应含
ETag和Cache-Control,记录 ETag 值 - 第二次请求(不清理缓存):检查请求头是否含
If-None-Match: "xxx";响应状态码应为304,且响应体为空 - 修改文件内容后再次请求:ETag 必须变化,响应应为
200并含新内容 - 多节点部署时,对比不同机器返回的 ETag 是否一致——不一致说明仍依赖 mtime,需排查是否漏关
etag on或未注入统一哈希











