Apache默认用INode、MTime和Size生成ETag,导致多服务器或重部署后ETag变化,使浏览器放弃缓存、浪费带宽;应禁用FileETag None并由应用层用内容哈希(如MD5)生成统一ETag。

Apache默认Etag生成逻辑会浪费带宽
Apache默认用 INode、MTime 和 Size 三元组生成ETag,这导致同一文件在不同服务器或重部署后ETag必然变化——哪怕内容完全一样。浏览器收到新ETag就会放弃缓存,发起完整请求,白白消耗带宽。
更糟的是,如果用了负载均衡或多台Apache节点,INode 值天然不一致,ETag彻底失效,所有条件请求都退化成全量响应。
解决办法不是禁用ETag(那会丢掉304优势),而是**强制只用内容哈希生成ETag**,让相同内容在任何机器上产生相同标识。
用FileETag指令关闭默认字段,只保留MD5
在虚拟主机或全局配置中添加:
<IfModule mod_headers.c>
# 关闭INode和MTime,只保留Size(但Size也不稳定,所以最终要禁用全部)
FileETag Size
</IfModule>但这还不够。真正可靠的做法是彻底禁用Apache自动生成,改由内容哈希驱动。所以应改为:
Apache Superset 是一个广泛采用的开源 BI 平台,用于 SQL 探索、图表构建和仪表板交付。当代理需要查询仓库数据、组装仪表板或使用成熟的分析界面解释指标而不是临时笔记本代码时,此技能非常有用。
-
FileETag None—— 禁用所有内置ETag生成 - 配合
mod_headers+mod_md5(或应用层)手动注入ETag - 若用静态资源且文件名含哈希(如
app.a1b2c3.js),直接设Cache-Control: immutable更高效,ETag反而冗余
对动态响应加ETag需在应用层计算内容摘要
Apache自身无法对PHP/Java等动态输出做内容哈希,必须由后端控制。常见做法:
- Java Web应用:用Filter拦截响应体,调用
MessageDigest.getInstance("MD5")计算字节数组摘要,写入ETag响应头 - PHP:在输出前用
md5(file_get_contents('php://output'))不现实,应改在模板渲染完成后、echo前计算缓冲区内容哈希 - 注意:不要在Filter里对已压缩(gzip)响应再算MD5,会导致ETag与客户端实际接收内容不匹配
示例Java Filter关键逻辑:
byte[] bodyBytes = getResponseBody(); // 实际需从HttpServletResponseWrapper获取
String etag = "\"" + DatatypeConverter.printHexBinary(MessageDigest.getInstance("MD5").digest(bodyBytes)) + "\"";
response.setHeader("ETag", etag);验证ETag是否生效及常见失效点
用 curl -I 检查响应头是否含 ETag,再发带 If-None-Match 的请求看是否返回 304 Not Modified:
curl -I -H 'If-None-Match: "abc123"' https://yoursite.com/some-page.html
容易被忽略的坑:
- Apache配置修改后没执行
apachectl graceful,改动未生效 - CDN或反向代理(如Nginx)覆盖了Apache的ETag,需检查中间层是否透传或重写了该头
- 响应含
Set-Cookie或Vary: Cookie,浏览器会拒绝缓存,ETag自然无意义 - 开发环境开了X-Debug或类似调试工具,可能自动注入额外响应头干扰ETag匹配逻辑
ETag的价值只在「内容不变但URL不变」的场景下成立;如果URL本身带版本号或哈希,优先走Cache-Control策略,别绕弯子硬套ETag。

















