Nginx下启用ETag与Last-Modified长期缓存静态文件的安全性及更新问题
嘿,这个问题问到点子上了——很多刚上手缓存配置的开发者都会踩这个坑,我给你把逻辑捋得明明白白:
核心结论:默认情况下,客户端不会自动同步更新
你给CSS/JS设了1年的缓存时长(比如expires 1y),同时开了ETag和Last-Modified,但在缓存有效期内,浏览器会直接用本地缓存,完全不会主动去服务器校验文件是否更新。只有当用户手动强制刷新(比如Ctrl+F5),或者缓存过期后,浏览器才会带着If-None-Match(对应ETag)或If-Modified-Since(对应Last-Modified)头去请求服务器,这时候才会同步新文件。
也就是说,如果你不改文件名,服务器端修改文件后,大部分用户在1年内都看不到更新,除非他们手动刷新——这显然不是你想要的结果。
ETag和Last-Modified的真实作用
这两个响应头是用来做协商缓存校验的,但它们的生效前提是:浏览器愿意发起校验请求。如果你的Cache-Control设了很长的max-age,浏览器会认为“缓存还新鲜,没必要去服务器问”,这两个头就发挥不了自动更新的作用。
不想改文件名?调整缓存策略是关键
如果你坚持不修改文件名(虽然我还是更推荐用文件名哈希的方案,比如style.abc123.css,这是业界通用的最优解),可以通过调整Cache-Control头来兼顾缓存性能和更新及时性:
推荐的Nginx配置示例
location ~* \.(css|js)$ { # 开启ETag和Last-Modified(默认开启,明确写上更稳妥) etag on; if_modified_since on; # 设置缓存控制:优先用本地缓存,同时后台异步校验更新 add_header Cache-Control "public, max-age=86400, stale-while-revalidate=31536000"; # 对应1年的过期时间,和max-age配合使用 expires 1y; }
这里的stale-while-revalidate是关键:
- 浏览器在
max-age(这里是1天)内直接用本地缓存,不发请求,保证性能; - 超过1天但在
stale-while-revalidate(1年)的有效期内,浏览器会在后台异步向服务器发起校验请求; - 如果服务器返回文件已更新,下次用户访问就会用新文件;如果没更新,继续用旧缓存。
容易遗漏的配置点
- 不要只依赖
expires指令:expires 1y会自动设置Cache-Control: max-age=31536000,但没有stale-while-revalidate的话,浏览器不会主动校验; - 确保ETag和Last-Modified没有被禁用:Nginx默认开启这两个功能,但有些自定义配置可能会通过
etag off或if_modified_since off关掉,一定要检查; - CDN层的缓存同步:如果你的站点用了CDN,必须在CDN后台也配置对应的协商缓存策略,不然CDN会一直缓存旧文件,客户端根本碰不到你的源服务器。
最后再啰嗦一句
虽然stale-while-revalidate能解决问题,但文件名哈希(内容哈希)才是静态资源缓存的最优解——每次文件内容变化,文件名就变,浏览器会直接请求新文件,完全没有缓存更新的顾虑。如果可以的话,建议你考虑这个方案。
内容的提问来源于stack exchange,提问作者user6188497

