Nginx静态资源协商缓存关键在于ETag与Last-Modified协同:默认仅发Last-Modified,需显式配置etag on启用弱ETag;两者不自动联动,客户端带If-None-Match时只校验ETag,带If-Modified-Since时只校验时间戳;推荐Cache-Control设为public, max-age=0, must-revalidate以强制验证。

要让静态资源真正“问过服务器再用”,关键不是堆满响应头,而是让 ETag 和 Last-Modified 各自发挥所长、不冲突、不冗余。Nginx 默认只发 Last-Modified,ETag 必须手动开启,且两者不会自动联动——实际是否走 304,取决于客户端带什么验证头、你配了什么指令、资源本身是否稳定。
明确 Nginx 的默认行为和触发逻辑
Nginx 对静态文件(如 .js、.css、.png)自动设置 Last-Modified,依据是文件系统中的 mtime(最后修改时间)。但默认不生成 ETag。只有在配置中显式写 etag on;,才会启用弱 ETag(格式为 W/"size-mtime")。
客户端发起条件请求时,行为如下:
- 只带
If-None-Match→ Nginx 仅比对 ETag - 只带
If-Modified-Since→ Nginx 仅比对 Last-Modified - 两个头都带 → 先校验 ETag,命中即返回 304,不再检查时间戳
判断是否真需要开启 ETag
ETag 不是“缓存加速器”,它解决的是 Last-Modified 精度不足的特定问题:
- 适合开启:内容频繁更新但 mtime 可能不变的场景,例如 Webpack 构建产物(含 content hash)、脚本动态生成的 JSON/JS/CSS、CMS 导出的静态页
- 不必开启:普通图片、字体、纯托管的 CSS/JS 文件——mtime 稳定可靠,加 ETag 反而引入额外 inode 查询开销,尤其在大文件或高并发下
Nginx 配置要点:简洁 + 匹配后端事实
ETag 生效的前提是响应中已存在 Last-Modified 头。如果后端 API 没返回该头,Nginx 的 etag on; 不会凭空生成 ETag:
- 先用
curl -I https://your-domain.com/static/app.js检查真实响应是否含Last-Modified - 若无,需在后端添加(如 Express 中用
res.set('Last-Modified', stats.mtime.toUTCString())) - Nginx 配置只需一行:
etag on;,放在server或location块内即可 - 避免同时手动用
add_header ETag ...又开etag on;,可能造成冲突
搭配 Cache-Control 明确协商意图
光有 ETag 或 Last-Modified 不够,客户端得知道“该去问服务器”。靠 Cache-Control 控制行为:
- 推荐配置:
public, max-age=0, must-revalidate—— 强制每次使用前验证,启用协商缓存 - 避免误解
no-cache:它语义正确(“用前必须验证”),但易被误读为“不缓存” - 注意:不要设
max-age过大还依赖协商,会导致长期不验证;也不要设max-age=0却漏掉must-revalidate,部分代理可能忽略验证

















