etag配合if-none-match用于精确判断资源是否变更,字节级一致才返回304;其本身无时间粒度,所谓“分钟级校验”需靠cache-control控制协商触发时机,并确保etag基于内容哈希生成。

ETag 配合 If-None-Match 不是用来实现“分钟级更新校验”的机制,而是用于精确判断资源是否发生变更——只要资源内容有哪怕一个字节的改动,ETag 就会变化,服务器就能立刻识别并返回新内容;若未变,则直接返回 304,不传资源体。它本身没有时间粒度(如“分钟级”)的概念,也不主动轮询或定时检查。所谓“分钟级增量更新校验”,其实是通过合理配置 ETag 生成策略 + 缓存响应头(尤其是 Cache-Control),让浏览器在缓存过期后自动发起带 If-None-Match 的协商请求,从而在资源更新后的首次有效请求中完成验证。
确保 ETag 由内容决定,而非时间戳
默认 Nginx 的 ETag 是基于文件 inode、大小和修改时间(mtime)生成的,这会导致:即使内容没变,仅因部署时文件时间戳刷新,ETag 也会变,引发无效 200 响应;反之,若内容变了但 mtime 被意外保留,ETag 可能不变,造成缓存污染。要支持真正可靠的增量校验,必须让 ETag 反映内容本质:
- 禁用默认 ETag(etag off;),改用 add_header ETag 手动注入——例如结合文件内容哈希(需构建阶段预计算并写入响应头)
- 或使用 Nginx 模块如 ngx_http_fingerprint_module,在运行时对响应体做轻量级哈希(如 xxHash),生成强 ETag
- 避免用 Last-Modified 替代 ETag,因其精度为秒级,同一分钟内多次更新无法区分
控制协商触发时机:用 Cache-Control 管理缓存生命周期
浏览器不会无条件发 If-None-Match。它只在本地强缓存(Cache-Control 或 Expires)过期后,才进入协商阶段。因此,“分钟级校验”的关键不在 ETag 本身,而在设定合理的缓存时长:
- 对 JS/CSS/图片等静态资源,设 Cache-Control: public, max-age=60(即 60 秒),强制浏览器每分钟重新协商一次
- 搭配 must-revalidate,确保 max-age 过期后绝不私自复用,必须向服务端确认
- 避免设过长 max-age(如 1 年)再依赖 ETag——那样更新后用户要等很久才触发协商,失去“分钟级”意义
服务端高效响应 If-None-Match 请求
Nginx 默认支持 If-None-Match 自动比对并返回 304,但前提是 ETag 已正确设置且未被覆盖。需注意:
- 确认响应中 ETag 值与 If-None-Match 头的值严格一致(含引号),Nginx 区分大小写和包裹格式
- 静态文件服务场景下,无需后端参与——Nginx 可完全自主完成比对与 304 返回,零额外开销
- 若资源走 CDN,需确保 CDN 透传 ETag 和 If-None-Match,并开启协商缓存支持(如 Cloudflare 的 “Origin Cache Control”)
配合前端构建实现真正增量感知
纯运行时 ETag 对“离线包”或“版本化资源”支持有限。更稳健的做法是在构建阶段注入版本标识:
- Webpack/Vite 构建时为每个资源生成 contenthash 文件名(如 app.a1b2c3.js),天然解决 URL 级缓存失效
- 同时在 index.html 或 manifest.json 中写入该 hash,并让 Nginx 根据此 hash 动态设置 ETag(例如 add_header ETag "$arg_v"; 配合 rewrite 规则)
- 前端加载时带上当前版本号作为查询参数(/js/app.js?v=a1b2c3),服务端据此生成确定性 ETag,使协商结果可预测










