NGINX 缓存对象正常但后台更新失效怎么办

排查 X-Accel-Expires 与 Cache-Control 响应头顺序,确认 stale-while-revalidate 是否丢失,并用显式配置灰度恢复

说明如何核对 X-Accel-Expires 与 Cache-Control 响应头顺序,区分缓存命中与 stale-while-revalidate 丢失,并安全灰度配置后台更新。

直接答案:如果 NGINX 代理缓存仍能出现 HIT,但对象过期时不再返回旧内容并后台更新,先不要清空缓存。先保存源站的原始响应头、NGINX 版本和缓存状态,再确认源站是否同时返回了 X-Accel-Expires 与带 stale-while-revalidate 的 Cache-Control。NGINX 上游在 2026 年 10 月报告了一个可复现问题:两个响应头仅交换顺序,就可能让缓存元数据中的后台更新窗口变成 0。当前更稳妥的处理是统一源站响应头,或者在 NGINX 中显式启用过期对象更新策略,并先灰度验证。

本文适用于已经使用 proxy_cache 的网站或 API。检查命令本身只读,不会停机,也不会修改缓存。真正改配置时需要 reload;正常 reload 不应中断已有连接,但错误的缓存规则可能把私人响应保存下来,所以必须先确认接口没有用户专属数据。

先确认是不是同一个问题

这不是“所有请求一直 MISS”的普通缓存故障。匹配本文现场通常同时满足三点:

  1. 同一 URL 预热后可以看到 HIT,说明缓存区、目录权限和缓存键基本可用;
  2. 对象过期后,并发请求一起回到源站,或者访问日志看不到预期的 STALE/UPDATING;
  3. 源站同时返回 X-Accel-Expires 与含 stale-while-revalidate 的 Cache-Control,交换两行顺序后结果改变。

如果请求从未命中过缓存,应先检查 Set-Cookie、Vary: *、proxy_cache_bypass、请求方法和缓存键。NGINX 官方文档说明,X-Accel-Expires 可以直接设置响应缓存时间;没有这个头时,才由 Expires 或 Cache-Control 决定缓存时间。带 Set-Cookie 或 Vary: * 的响应也可能不被缓存。

第一步:保存版本、完整配置和原始响应头

先在服务器 SSH 中执行以下只读检查。把示例域名换成实际测试 URL。不要在公开工单里粘贴认证 Cookie、上游内网地址或含 Token 的查询参数。

nginx -V 2>&1
sudo nginx -T > /tmp/nginx-config-$(date +%F-%H%M).txt 2>&1
curl -sS -D /tmp/origin-headers.txt -o /dev/null https://example.com/test-path
sed -n '1,40p' /tmp/origin-headers.txt

nginx -V 用来记录版本和构建参数,nginx -T 会展开当前实际加载的配置。第三条命令只下载响应头和丢弃正文,不会改服务器。如果站点前面还有 CDN,公网响应头可能已被改写;应在有授权的前提下,使用源站域名或临时 hosts 方式再取一份,避免把 CDN 行为误判为 NGINX 行为。

重点检查下面两行是否都存在,以及顺序是否会随应用框架、中间件或不同路由变化:

X-Accel-Expires: @1790864859
Cache-Control: max-age=60, stale-while-revalidate=600, private

上游 Issue 的复现条件中,以上顺序能够保留 600 秒更新窗口;反过来先返回 Cache-Control 时,缓存对象仍可能写入,但内部更新窗口变为 0。这个结论目前只应描述为“已报告且可复现的上游问题”,不能写成所有 NGINX 版本都存在,也不能推断已经在哪个版本修复。

第二步:把缓存状态写进测试日志或响应头

只看浏览器是否变快无法判断缓存阶段。可以在一个测试 location 或受控灰度入口临时输出 $upstream_cache_status。不要给全站永久暴露内部调试信息。

location /cache-test/ {
    proxy_pass http://backend;
    proxy_cache site_cache;
    add_header X-Cache-Debug $upstream_cache_status always;
}

保存前先备份原配置,然后执行语法检查:

sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.before-cache-test
sudo nginx -t

只有看到 syntax is ok 和 test is successful 才能 reload。若配置由宝塔面板生成,应在面板对应站点的反向代理配置中修改,并另外保存一份文本备份,避免面板保存时覆盖手工文件。

常见状态可以这样判断:

状态含义下一步
MISS缓存中没有当前键再请求一次;持续 MISS 时检查响应头、缓存键和绕过条件
HIT返回新鲜缓存说明基本缓存链路可用,继续等待过期窗口
EXPIRED对象过期并同步回源若并发回源明显,核对 stale/update 配置
STALE/UPDATING返回旧对象,同时由请求更新缓存符合后台更新预期,再核对源站只收到少量更新请求
BYPASS规则要求跳过缓存检查 Cookie、Authorization、查询参数和 bypass 条件

第三步:按结果选择修复路径

路径一:能改源站时,统一缓存控制来源

最小风险方案是让缓存生命周期只有一个权威来源。应用如果已经用 X-Accel-Expires 控制 NGINX 缓存,就不要再依赖另一个顺序不稳定的 Cache-Control 扩展来决定后台更新;如果要由标准 Cache-Control 管理,则应确认应用、框架和 CDN 不会再插入冲突的 X-Accel-Expires。

不要为了“让它缓存”直接删除 private。带登录态、购物车、后台页面、账户余额或用户专属 API 的响应通常不应进入共享缓存。修复前先用无 Cookie 的公开资源建立最小复现,再把规则逐步扩大到明确可共享的路径。

路径二:不能马上改源站时,在 NGINX 显式配置更新行为

NGINX 官方文档说明,proxy_cache_background_update on 会在返回过期对象的同时发起后台子请求,但还需要允许在更新期间使用旧对象。可以只对确认可共享的公开路径灰度配置:

location /public-api/ {
    proxy_pass http://backend;
    proxy_cache site_cache;

    proxy_cache_background_update on;
    proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
    proxy_cache_valid 200 60s;

    add_header X-Cache-Debug $upstream_cache_status always;
}

60s 只是便于灰度验证的示例,不是通用生产值。实际时间应按内容变化频率和允许陈旧的范围决定。错误页、库存、价格、登录态和订单接口不能照抄这组规则。

谨慎使用 proxy_ignore_headers Cache-Control X-Accel-Expires。该指令会让 NGINX 忽略源站对缓存的控制,虽然可以绕开冲突头,却可能把本来禁止共享的响应缓存下来。只有在 location 已严格限定为公开、无身份差异的内容,并且缓存时间完全由 NGINX 管理时才考虑使用。

路径三:一直 MISS 或 BYPASS,回到普通缓存条件排查

如果两次相同请求都没有 HIT,本问题还没成立。先检查请求方法是否为 GET/HEAD、缓存目录是否可写、响应是否带 Set-Cookie、是否存在 Vary: *、缓存键是否包含每次变化的参数,以及 proxy_no_cache/proxy_cache_bypass 是否被触发。需要先解决这些条件,再测试过期更新。

第四步:灰度 reload,并验证后台更新

先检查配置,再平滑加载:

sudo nginx -t && sudo systemctl reload nginx
systemctl is-active nginx
curl -sS -I https://example.com/cache-test/resource

确认服务仍为 active,第一次请求是 MISS,第二次出现 HIT。等待对象超过测试 TTL 后,用少量并发请求观察更新过程:

seq 1 10 | xargs -n1 -P10 -I{} curl -sS -o /dev/null \
  -w '%{http_code} %{time_total}\n' https://example.com/cache-test/resource

这条命令会产生 10 个请求,先在低流量测试路径执行,不要对生产源站做大并发压测。预期结果是客户端仍得到正常状态码,访问日志能看到旧对象被使用或更新状态,而源站不会同时收到 10 个完整刷新请求。还要检查正文是否确实更新,不能只看 HTTP 200。

费用、停机和数据风险

  • 费用:检查与 reload 本身通常不产生额外软件费用;缓存失效会增加源站 CPU、带宽和上游请求费用。
  • 停机:正确的 NGINX reload 通常不会中断已有连接;配置测试失败时不要 reload。重启服务的风险高于 reload,本文不要求重启。
  • 数据:不要直接删除整个缓存目录。清空缓存会制造回源洪峰,也会丢失复现证据。
  • 隐私:强制忽略 private、Cookie 或 Authorization 边界,可能把一个用户的响应发给另一个用户,这是本次修改的最高风险。

回退方法

如果灰度后出现内容陈旧、私人响应命中或源站请求增加,立即恢复备份配置并再次检查:

sudo cp /etc/nginx/nginx.conf.before-cache-test /etc/nginx/nginx.conf
sudo nginx -t && sudo systemctl reload nginx

如果实际修改的是站点单独配置文件,应恢复对应文件,而不是覆盖主配置。回退后再次请求测试 URL,确认调试响应头消失、NGINX 为 active,并核对业务正文和登录态没有串缓存。

与其他 NGINX 问题怎么区分

反向代理 TLS 与基础转发应先参考NGINX HTTPS 反向代理配置。若表现为等待源站后返回网关超时,应走NGINX 504 排查。若日志是 upstream sent too big header,应检查响应头体积与缓冲区,而不是缓存更新窗口,可参考上游响应头过大排查。需要判断 CDN、全球加速和源站直连边界时,可再看CDN 与源站入口选型。

官方与上游依据

待确认事实:本稿没有需要黑鲨云补充确认的业务事实。NGINX 维护者已复现响应头顺序问题,修复 PR #1822 仍在审查;截至 2026 年 10 月 9 日,Issue 仍开放,未见已合并提交、里程碑或正式修复版本。

云服务器教程Docker 拉取镜像时遇到 lease does not exist 怎么排查2026-10-15云服务器教程NGINX gRPC 上游空闲连接被 RST 重置怎么排查2026-10-14云服务器教程BuildKit prune 后 metadata_v2.db 还在变大怎么处理2026-10-13

加入开发者交流社区

与全球开发者、运维和工作室一起交流技术、分享经验、配置、账号与最新优惠信息

  • 云平台使用交流
  • 资源优惠信息
  • 最新教程与资讯
  • 开发者经验分享
联系 Telegram 客服
加入开发者交流社区