直接禁用 fileetag 是提升浏览器缓存效率最有效且易被误操作的关键步骤,apache 默认的 inode mtime size 组合在多服务器、构建部署等场景下极易导致 etag 频繁变动,引发无效 200 响应;应全局配置 fileetag none,并对静态资源采用文件名哈希+immutable 策略,动态响应则由后端按内容生成强 etag 并配合 cache-control 触发 304 协商。

直接禁用 FileETag,而不是调整它——这是提升浏览器缓存速度最有效、也最容易被误操作的一步。
Apache 默认用 FileETag INode MTime Size 生成 ETag,但这三个字段在多数真实部署场景中都不稳定:
- 多服务器或容器环境里,同一文件 inode 不同
- 文件重写(如构建、部署)必然更新 mtime,哪怕内容没变
- 文件大小可能因压缩、BOM、换行符差异而波动
结果就是:资源没变,ETag 却变了 → 浏览器拿旧 ETag 发 If-None-Match → 服务端无法匹配 → 返回 200 + 完整响应体 → 白白浪费带宽和时间。
关闭默认 FileETag 是必须动作
在 Apache 主配置(如 httpd.conf 或虚拟主机段)中添加:
FileETag None
不是 FileETag MTime,也不是 FileETag Size,必须是 None。
这个指令要放在 <ifmodule mod_headers.c></ifmodule> 块内或全局生效位置,确保它被加载模块支持。
静态资源不靠 ETag,靠文件名哈希 + immutable
JS/CSS/图片这类不变或低频变更的资源,根本不需要 ETag 协商:
- 构建时把内容哈希嵌入文件名,例如
app.f3a8b2e7.js - 配置响应头:
Cache-Control: public, max-age=31536000, immutable - 同时移除 ETag:
Header unset ETag
这样浏览器一次缓存,永久复用,零协商开销,彻底规避 ETag 不一致问题。
动态响应才需要 ETag,且必须由后端生成
Apache 本身不生成 ETag,只透传后端设置的值。所以:
- PHP 中:
header('ETag: "' . md5($response_body) . '"'); - Java/Spring Boot 中:启用
ShallowEtagHeaderFilter或手动注入 - JSON API:用响应体 SHA-256 哈希截取前 8 位,加
W/前缀,如W/"c7e9a2f1"
关键点:ETag 必须随内容真实变化而变,不能含时间戳、随机数、session ID 等非内容相关字段。
搭配 Cache-Control 才让 ETag 发挥作用
单独设 ETag 没意义。必须配合缓存策略触发协商流程:
- 首次响应带:
ETag,Last-Modified,Cache-Control: public, max-age=3600 - 一小时后浏览器自动发起带
If-None-Match和If-Modified-Since的请求 - Apache 收到匹配的
If-None-Match,直接返回304 Not Modified,无响应体
验证是否生效:用浏览器开发者工具 Network 面板看第二次请求状态码是否为 304,响应头是否为空,Size 是否显示 (from memory cache) 或 (from disk cache)。
不复杂但容易忽略










