· Jeff · 技术文章  · 6 分钟阅读

改了静态站内容,线上却不更新?nginx 缓存排障实录

静态站改完内容部署后线上还是旧页面,排查半天发现根因是宝塔 nginx 的全局代理缓存。一篇讲透判断方法和治本方案。

静态站改完内容部署后线上还是旧页面,排查半天发现根因是宝塔 nginx 的全局代理缓存。一篇讲透判断方法和治本方案。

改了静态站内容,线上却不更新?nginx 缓存排障实录

静态站(Docker + Nginx 部署)改完内容、重新构建部署后,线上访问还是旧页面。带 ?v=随机数 能拿到新内容,不带参数就是旧的——典型的缓存层作祟。本文记录完整排查过程和治本方案。

一、现象

我维护的一个静态博客站(Astro 构建,Nginx 容器托管),在某次加了一个新栏目后:

  • 重新构建 Docker 镜像并重启容器 ✅
  • 容器内直接 cat 生成的 HTML,新内容明明在
  • 但浏览器 / curl 访问线上域名,看到的还是旧页面

最诡异的是:curl "https://站点/页面/?v=12345"(带随机参数)能拿到新内容,不带参数就是旧的。

二、第一反应:是不是 CDN 缓存?

我的站点前面套了 Cloudflare,第一反应是 CDN 边缘缓存。

结果检查响应头:

cf-cache-status: DYNAMIC      # Cloudflare 说自己没缓存
last-modified: Mon, 17 Aug 2026 17:15:50 GMT   # 旧时间

cf-cache-status: DYNAMIC 意味着 Cloudflare 认为这个响应不该被缓存。那 last-modified 为什么是旧的?

三、关键判断:对比「源站直连」和「外部访问」的 Last-Modified

这是整个排查里最有用的一步——对比同一资源在「源站直连」和「外部访问」下的响应头差异

# 1. 直连源站(跳过所有前置层)
curl -sI http://127.0.0.1:3001/collections/
# Last-Modified: Mon, 17 Aug 2026 17:53:30 GMT   ← 新内容的时间

# 2. 走完整链路(域名访问)
curl -sI https://你的域名/collections/
# Last-Modified: Mon, 17 Aug 2026 17:15:50 GMT   ← 旧内容的时间

同一个页面,源站返回新时间,外部访问返回旧时间——说明问题不在源站,而在源站和外部之间的某一层缓存。

既然 Cloudflare 显示 DYNAMIC(没缓存),那嫌疑就落到源站前面的反向代理上。

四、根因:宝塔 nginx 的「全局代理缓存」对全站生效

我用的服务器面板(宝塔)在 nginx 全局配置里有一个 proxy.conf,里面有这样一行:

# 全局代理缓存配置(http 级)
proxy_cache_path /www/server/nginx/proxy_cache_dir levels=1:2 keys_zone=cache_one:20m inactive=1d max_size=5g;
proxy_cache cache_one;

关键在最后一行:proxy_cache cache_one; 写在 http 级(全局),对所有站点生效

而我的站点自己的配置里虽然没写 proxy_cache cache_one;,但它继承了全局配置——所有经 nginx 反代的响应都会被缓存inactive=1d,最长缓存 1 天)。

所以流程是:

浏览器 → Cloudflare(DYNAMIC 不缓存) → 宝塔 nginx(proxy_cache 缓存了旧 HTML) → Docker 容器(其实已是新内容)

之前我还以为 proxy_cache_valid 200 1h;(站点配置里的一行)是缓存源,但真正起作用的其实是全局那行 proxy_cache cache_one;

五、治本方案:绕过缓存 + 清空旧缓存

1. 给站点加绕过指令

在站点的 nginx 配置 location / 里加上:

location / {
    proxy_pass http://127.0.0.1:3001;
    ...
    proxy_no_cache 1;        # 不写入缓存
    proxy_cache_bypass 1;    # 绕过已有缓存,直接回源
}
  • proxy_no_cache 1;:告诉 nginx 这个响应不要写进缓存
  • proxy_cache_bypass 1;:即使缓存里有,也直接回源站拿新的

2. 清空已有缓存

rm -rf /www/server/nginx/proxy_cache_dir/*

3. 重载 nginx

nginx -s reload   # 或 systemctl reload nginx

六、验证

改完再访问,Last-Modified 变成新内容的时间,新栏目正常显示:

last-modified: Mon, 17 Aug 2026 17:53:30 GMT   ← 已是新时间

之后每次改内容,部署完刷新就能看到,不用再等缓存过期。

七、经验总结

  1. ?v=随机数 能拿到新内容 = 有缓存层。不同 URL 会绕过缓存,所以能拿到新的;但这不是根治,只是绕过。
  2. 判断缓存在哪层:对比「源站直连」和「外部访问」的响应头(尤其 Last-Modified / Age)。不一致 = 中间有缓存。
  3. cf-cache-status: DYNAMIC 不代表没有缓存——它只说明 Cloudflare 没缓存,源站前面的反向代理可能缓存了
  4. 面板类服务器(宝塔等)的全局 nginx 配置要留个心眼proxy.conf 里的 proxy_cache cache_one; 是 http 级,对所有站点生效,很容易成为隐藏的缓存源。
  5. 治本一定要用 proxy_no_cache + proxy_cache_bypass,而不是只清一次缓存(清完还会再缓存旧的)。

相关阅读

  • 如果你也遇到「改了 Docker 容器内容但线上不更新」,先按上面的三步排查:容器内看内容 → 对比 Last-Modified → 找缓存层。

☕ 如果这篇文章对你有帮助

欢迎请 Jeff 喝杯咖啡,支持我持续分享更多软件技巧~

打赏功能即将上线,先点个赞也是支持 ❤️

返回博客

相关文章

查看全部 »
进程还在,端口已死:一个单线程 HTTP 服务的「假活」陷阱,和我的四层加固

进程还在,端口已死:一个单线程 HTTP 服务的「假活」陷阱,和我的四层加固

我自建的一个统计面板曾经挂了整整 16 天我才发现:进程从没退出、端口一直在 LISTEN、CPU 占用是零,看上去「健康得不得了」,可所有新连接都拿不到响应,公网一律 504。根因是单线程 HTTPServer 没有 socket 超时,被一条半开连接永久阻塞;更值得记的是第二层——进程监督器只认「进程存活」,这种假活对它完全不可见。这次我给它上了四层加固,也第一次想明白:为什么「重启脚本」这种修复,会被下一次部署悄悄冲掉。

1.3GB 内存的服务器上,浏览器自动化必然 OOM:我最后用纯 HTTP 重写了一遍签到

1.3GB 内存的服务器上,浏览器自动化必然 OOM:我最后用纯 HTTP 重写了一遍签到

一个每天跑的签到脚本,用 Playwright 时 6 分钟才启动完浏览器、随后必崩。我一度以为是脚本写得不对,换了三种写法都没用。真正的结论是:在 1.3GB 内存、swap 全满的机器上,浏览器自动化注定失败——不是代码问题,是物理问题。更值得记的是第二层翻车:当时得出的「必须借浏览器上下文」这个结论本身就是错的,而它被固化进了代码,白折腾了一周。

FileBrowser 免登录图片直链:容器部署 + Notion 嵌入完整指南

FileBrowser 免登录图片直链:容器部署 + Notion 嵌入完整指南

想让 Notion 页面直接嵌入服务器上的图片,又不想把 FileBrowser 的管理登录暴露给所有人?核心思路不是 FileBrowser 自带功能,而是 nginx 层用 alias 把图片目录直接映射出来绕过认证。这篇是完整实战指南,含部署、直链、验证和排障。