要在 nginx 中实现可靠的 last-modified 协商缓存,需确保 nginx 正确生成并传递该头(静态文件默认开启,禁用 last_modified off;代理场景依赖上游)、配合 cache-control public, max-age=0, must-revalidate 触发浏览器协商请求,并按需启用 etag on 提升精度。

要在 Nginx 中配合 Last-Modified 实现可靠的协商缓存,关键不是“堆砌头信息”,而是让 Nginx 正确生成、传递并尊重这个时间戳,同时确保客户端行为与服务端逻辑对齐。Nginx 默认就支持它——但默认行为常被误关或覆盖,导致 304 始终不生效。
确认 Last-Modified 是否真正发出
Nginx 对静态文件(如 .js、.css、.png)自动添加 Last-Modified 响应头,前提是:
• 没有显式关闭 last_modified off;
• 资源由 Nginx 直接提供(非 proxy_pass 到后端),且文件系统中存在对应文件
• 文件有可读的修改时间(mtime)
验证方式:用 curl -I https://yoursite.com/app.js 查看响应是否含 Last-Modified。若缺失,检查配置中是否误写了 last_modified off;,或资源实际来自代理而非本地文件。
让浏览器真正发起协商请求
仅返回 Last-Modified 不够,浏览器必须知道“该去问”。这靠 Cache-Control 控制:
- 配
public, max-age=0, must-revalidate:每次使用前强制验证,启用协商缓存 - 避免只写
no-cache:语义正确但易被误解;明确加must-revalidate更稳妥 - 不要配
no-store或private(除非敏感场景):它们会跳过协商流程
示例配置:
location ~* \.(js|css|png|jpg|gif)$ {
expires 1h;
add_header Cache-Control "public, max-age=0, must-revalidate";
# last_modified on; # 默认开启,无需重复写
}
处理代理或动态内容时的常见断点
当 Nginx 作为反向代理(proxy_pass)时,Last-Modified 由上游服务决定,Nginx 不会自动生成:
- 检查上游响应:用
curl -I http://upstream/api/data确认含Last-Modified - 禁止 Nginx 覆盖:不要在 location 中写
add_header Last-Modified ""或unset类指令 - 若上游不发该头,需在应用层补上(如 Express 中
res.set('Last-Modified', new Date().toUTCString()))
注意:Nginx 不会“修复”缺失的 Last-Modified,也不会用文件时间替代动态接口的响应时间。
结合 ETag 提升精度(按需启用)
Last-Modified 精度为秒,若资源一秒内多次更新,或内容未变但 mtime 被刷新,就会误判。此时可启用 ETag 作为补充:
- 加
etag on;(放在 server 或 location 块内),Nginx 生成弱 ETag(W/"size-mtime") - 客户端带
If-None-Match和If-Modified-Since时,Nginx 优先校验 ETag,命中即返 304,不再查时间戳 - 仅对内容频繁更新但 mtime 不稳定的情况开(如 Webpack 构建产物、CMS 导出页);普通图片/字体不必开,反而增加 inode 查询开销











