304不是错误而是缓存命中后的正常响应;若耗时偏高(如>200ms),说明条件请求校验环节成瓶颈,需检查If-Modified-Since/If-None-Match是否被Nginx快速比对、后端是否缺失Last-Modified或ETag、文件系统stat开销、Cache-Control配置及proxy_cache_revalidate等链路问题。

304 不是错误,而是缓存命中后的正常响应;但若返回 304 却耗时明显偏高(比如 >200ms),说明协商过程本身成了瓶颈——问题不在传输内容,而在“确认要不要传”的环节拖慢了。
检查条件请求头是否触发冗余校验
浏览器发来的 If-Modified-Since 或 If-None-Match 头,必须能被 Nginx 快速比对。若后端未提供 Last-Modified 或 ETag,Nginx 就会退化为全量读取文件再计算哈希,导致延迟飙升:
- 静态资源:确保
expires和add_header ETag ...同时启用;Nginx 默认对 static 文件生成弱 ETag,但需确认未被etag off;关闭 - 动态接口:后端必须主动输出
Last-Modified或ETag响应头;若后端缺失,Nginx 无法跳过内容生成,只能回源重跑逻辑 - 避免混合策略:同一资源不要同时依赖时间戳和 ETag 校验,Nginx 会按顺序逐个验证,任一失败就降级为 200
定位 ETag 计算或文件 stat 开销
Nginx 对静态文件生成 ETag 时,默认基于 mtime + size;若文件系统慢(如 NFS、加密盘)、inode 查询卡顿,或文件过大(>100MB),stat() 调用就会阻塞 worker:
- 用
strace -p $(pgrep nginx) -e trace=stat,fstat观察是否频繁调用stat且耗时高 - 对高频小文件,可改用
etag on;(默认)并确保文件系统无延迟;对大文件或网络存储,建议后端直接提供强 ETag,Nginx 不参与生成 - 禁用不必要的模块:如
ngx_http_realip_module在日志中记录真实 IP 时若配置不当,也可能干扰 header 解析流程
验证客户端缓存策略是否引发重复协商
看似是 304,实则因客户端缓存控制混乱,导致每次请求都走完整协商流程:
- 检查响应头中
Cache-Control是否含no-cache或max-age=0:这两者都会强制浏览器每次发条件请求,哪怕资源未变 - 对比首次 200 与后续 304 的
Age和Expires值:若Age持续为 0,说明代理层(如 CDN)未缓存,所有请求都打到 Nginx - 用 curl 模拟复现:
curl -I -H "If-None-Match: \"xyz\"" https://yoursite.com/file.js,观察响应时间是否稳定;再换一个无效 ETag 测试是否同样慢——若都慢,问题在服务端校验;若仅无效 ETag 慢,说明 Nginx 正确 fallback 到 200,但后端响应慢
排查 upstream 协商链路中的隐性开销
当 Nginx 作为反向代理时,304 的判定逻辑可能跨多层:
- 若启用了
proxy_cache,需确认proxy_cache_valid 304 ...已配置;否则 Nginx 收到上游 304 后,仍会重新构造响应头并写入缓冲区,而非直接透传 - 检查
proxy_buffering是否开启:开启时,即使上游快速返回 304,Nginx 也会等待缓冲区填满或超时才发送,造成“假延迟” - 上游服务若自身也做 ETag 校验(如 Node.js 的 express.static),而 Nginx 又重复校验,就会形成双重开销;建议由上游统一负责,Nginx 仅透传



















