Apache 2.4.18缓存配置异常:iOS/Safari无法返回200/304状态码
我之前在维护Apache服务的时候,刚好碰到过和你一模一样的场景——默认配置下ETag在iOS Safari里不按预期工作,文件更新后缓存死活不刷新,折腾了好一阵才理清问题。结合你的情况,咱们来一步步分析解决:
为啥会出现这种矛盾的情况?
1. Apache默认ETag的生成“坑”
Apache 2.4.18默认是用inode-size-mtime三个值生成ETag的,简单说就是文件的索引节点、大小和修改时间。但如果你的服务器用了特殊存储(比如NFS挂载、云存储卷,甚至用cp命令直接覆盖文件而不是编辑),inode可能会突然变化——这时候哪怕文件内容只改了一个字符,新ETag也会完全不一样,Safari拿到的新ETag和缓存里的不匹配,理论上应该返回200,但有时候Safari的缓存逻辑会“钻牛角尖”,直接忽略新的验证信息。
2. Safari的缓存策略太“固执”
iOS上的Safari对缓存的处理比Chrome、Firefox激进得多,如果响应头里没有明确的Cache-Control指令,它可能会直接忽略ETag和Last-Modified,硬用本地缓存。而Apache默认配置里是不会主动添加Cache-Control的,这就给Safari的“任性”留了空间。
一步步排查&解决
1. 先确认Apache的ETag是否正常输出
先在服务器上用curl命令看看当前文件的响应头,验证ETag是否真的生效了:
curl -I https://你的域名/路径/到/你的文件
正常情况下,输出里应该能看到ETag和Last-Modified字段,比如:
ETag: "5f8a1b2c-1234"
Last-Modified: Tue, 20 Oct 2020 12:34:56 GMT
如果看不到这两个字段,那说明默认配置可能被意外修改了,但你说没动过配置,大概率是正常的。
2. 修改ETag生成规则,规避inode问题
既然inode可能搞事情,咱们直接把它从ETag生成规则里去掉。打开/etc/apache2/apache2.conf(或者你的虚拟主机配置文件),添加一行:
FileETag Size MTime
这时候ETag只会基于文件大小和修改时间生成,不管inode怎么变,只要文件内容更新,ETag就会跟着变。
添加完后重启Apache生效:
sudo systemctl restart apache2
3. 给Safari明确的缓存规则(关键!)
这一步是解决Safari问题的核心——给响应头加上Cache-Control指令,告诉Safari该怎么缓存:
在虚拟主机配置里添加这段规则(可以放在<VirtualHost>标签内):
<FilesMatch "\.(html|css|js|png|jpg|gif)$"> Header set Cache-Control "public, max-age=3600, must-revalidate" </FilesMatch>
解释下这几个参数:
public:允许浏览器、CDN这类公共缓存存储文件max-age=3600:文件缓存1小时,1小时内不用找服务器验证must-revalidate:缓存过期后,必须向服务器验证文件是否更新(这时候ETag就会发挥作用,返回304或者200新内容)
同样,重启Apache让配置生效。
4. 测试验证
先在服务器上用curl模拟浏览器的缓存验证请求,确认服务器能正确返回304:
curl -I -H 'If-None-Match: "你之前拿到的ETag值"' https://你的域名/路径/到/你的文件
如果返回304 Not Modified,说明服务器端的验证逻辑是正常的。
然后在iOS Safari里测试:先清除浏览器缓存,访问一次文件,然后修改服务器上的文件,再刷新页面——这时候应该能拿到新的200内容,或者缓存过期后拿到304。
如果还是有问题,可以试试Safari的私有浏览模式,有时候普通模式的缓存会更顽固。
额外小技巧
如果还是碰到Safari缓存“死不悔改”的情况,建议给静态资源文件名加版本号(比如style.v2.css),这是前端缓存优化的常用手段,直接绕过浏览器的缓存验证机制,彻底解决缓存更新问题。
内容的提问来源于stack exchange,提问作者M Katz

