nginx 不生成、不校验、不决定 etag,仅透传条件请求头并复用后端返回的 304 响应;关键在于后端稳定输出语义准确的 etag、客户端携带 if-none-match、nginx 正确缓存并转发,三者协同实现协商缓存以节省带宽。

Nginx 本身不主动“校验 ETag”,也不会在缓存过期后自动发起 If-None-Match 请求——它只在收到客户端带条件头(如 If-None-Match 或 If-Modified-Since)的请求时,才转发这些头给后端,并根据后端返回的 304 Not Modified 复用本地缓存体。所谓“利用 ETag 减少后端带宽”,本质是让整个链路支持协商缓存:客户端→Nginx→后端,三者协同,使多数请求最终以 304 响应结束,仅传输响应头(200–500 字节),而非完整响应体。
关键不在 Nginx 配什么指令,而在整条链路是否闭环
Nginx 不生成、不校验、不决定 ETag 的值,它只是透传和响应。真正起作用的是:
- 后端是否稳定输出语义准确的
ETag(如基于内容哈希) - 客户端是否在后续请求中携带
If-None-Match - Nginx 是否正确缓存了含
ETag的原始响应,并在收到条件请求时原样转发
只要这三点成立,Nginx 就能复用缓存体,节省响应体带宽。
确保后端输出可验证的 ETag
静态资源(.js/.css/.png)由 Nginx 自身托管时,默认启用 etag on,且基于文件大小+修改时间生成 ETag,基本可用。
动态接口(如 /api/user)则需后端主动实现:
- 首次响应必须包含
ETag: "abc123"(建议用内容 MD5 或 SHA256,避免时间戳或随机数) - 收到
If-None-Match: "abc123"时,严格比对:完全一致 → 返回304 Not Modified -
304响应体必须为空,禁止设置Content-Length: 0或分块编码(Transfer-Encoding: chunked),否则 Nginx 可能解析失败而降级为200
Nginx 端需启用基础缓存并保留 ETag 头
无需额外“开启 ETag 校验”,但必须保证缓存流程不破坏协商机制:
- 启用缓存区域(
http块):proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m inactive=1h max_size=1g;
- 在
location中启用缓存,并允许缓存含ETag的响应:proxy_cache my_cache; proxy_cache_valid 200 302 10m; # 默认会缓存 ETag、Last-Modified 等头,无需显式配置
-
不要忽略
Vary或Set-Cookie:若后端返回Set-Cookie,Nginx 默认跳过缓存;如接口无用户态,加:proxy_ignore_headers Set-Cookie Vary;
让客户端真正发起条件请求
浏览器是否发 If-None-Match,取决于它本地是否有该资源的 ETag 缓存,以及是否过了 Cache-Control 或 Expires 的强缓存期。
- 推荐组合策略:
- 强缓存:
Cache-Control: public, max-age=300(5 分钟内直接读本地) - 协商缓存:5 分钟后,浏览器自动带
If-None-Match发起验证
- 强缓存:
- 若想更早触发验证(比如 HTML 页面),可设短强缓存 + 永久可协商:
add_header Cache-Control "public, no-cache, must-revalidate"; # 浏览器每次都会发条件请求,但 Nginx 只在缓存有效期内直接返回 HIT
避免并发击穿,防止条件请求被放大
多个请求同时命中过期缓存,若都转发 If-None-Match,会造成回源压力倍增:
- 开启缓存锁,确保只有一个请求去验证:
proxy_cache_lock on; proxy_cache_lock_timeout 5s;
- 允许前台返回旧缓存,后台异步刷新(提升用户体验):
proxy_cache_use_stale updating; proxy_cache_background_update on;
不复杂但容易忽略











