
最近折腾了一个“明明已经修好,读者却还是打不开”的链接,踩了几个缓存相关的坑,这篇把问题彻底说清楚:为什么页面恢复了,404 和旧重定向却还活着,以及该从哪一层开始排查。
你收到链接失效的报告,找到原因,恢复页面或者加上重定向,自己测试也正常。结果一个小时后,又有人反馈:还是坏的。
你没看错,对方也没看错。从服务器到读者之间不止一个位置会缓存 HTTP(超文本传输协议)响应,错误响应和成功页面一样会被缓存,重定向也一样 Sometimes 缓存的时间还远超预期。
下面按缓存层级逐个拆开:它为什么会记住错误响应,以及怎么判断到底是哪一层在“拖后腿”。
浏览器会按照 HTTP 缓存规则缓存响应,规则定义在 RFC 9111 中。最关键的响应头是 Cache-Control,老一些的方案还会用 Expires。
按照这套规则,404 响应默认可以被缓存。也就是说,即使没有显式设置缓存响应头,缓存层也可能通过启发式规则存储并复用错误响应。实际使用中,浏览器对错误响应通常比较保守,但如果服务端通过一条笼统的配置规则,给包括 404 在内的所有响应都加上了类似下面的响应头:
Cache-Control: public, max-age=86400 那么浏览器完全有理由在一天之内继续显示这个 404。
怎么判断:让对方打开无痕窗口,或者用不带缓存的 curl 测试。如果 curl 能看到修复后的页面,浏览器却看不到,问题就在本机缓存。
永久重定向是最容易让人意外的情况。301 和 308 默认都可以被缓存,浏览器通常还会缓存得很激进。
比如之前误把 /pricing 永久重定向到了 /plans,后来又删掉了这条规则。访问过它的浏览器可能仍然会直接跳到 /plans,根本不再向服务器询问 /pricing 的最新状态,而且这个状态可能维持很久。
所以,迁移期间“先临时用一下 301”往往很难收场。只要还不能确定重定向永远不会变,使用 302 或 307 会更容易撤回。
怎么判断:在浏览器开发者工具的网络面板里勾选“Disable cache”,再对比请求结果。如果重定向标记为“from disk cache”或“from memory cache”,基本可以确定是浏览器缓存。
如果网站放在 CDN(内容分发网络)或缓存代理后面,例如 Varnish、nginx 的 proxy_cache,或者云厂商的边缘网络,这一层可能已经缓存了 404。
很多 CDN 会单独设置错误响应的缓存时间,通常叫“negative caching”或“error caching”。不同厂商、不同套餐的默认值并不一样。有些服务默认只缓存 404 一小段时间,目的是防止大量不存在的 URL 把源站打挂。
这也是“明明修好了,但所有人都还是打不开”最常见的原因:源站已经恢复正常,边缘节点却还在返回之前存下来的错误,直到缓存时间,也就是 TTL(生存时间)过期。
怎么判断:查看响应头。多数 CDN 会暴露缓存状态,常见的有 Age、X-Cache: HIT,也可能是厂商自定义的响应头。如果 Age 大于你修复后经过的时间,就说明看到的是旧副本。
curl -sI https://example.com/docs/install | grep -iE '^(HTTP|age|cache-control|x-cache|cf-cache-status)'
怎么修复:从 CDN 中清理这个 URL,同时检查错误响应缓存配置。
框架和 CMS(内容管理系统)通常都有自己的页面缓存,例如构建时生成的静态 HTML、全页缓存插件、内存中的路由缓存。如果 404 已经在这一层生成并缓存下来,那么修改数据库里的内容并不会立即改变最终响应,必须先重建缓存。
静态站点生成器更特殊:如果页面在构建时不存在,那么无论你把源码修好多少次,在下一次构建和部署之前,它都会继续缺失。
怎么判断:绕过 CDN,直接请求源站,例如使用内部域名或者 curl --resolve,看看源站自身是否仍然返回 404。
如果链接失效是因为域名出了问题,比如 docs.example.com 缺少 DNS(域名系统)记录,那么就要考虑 DNS 缓存。
域名解析器会按照记录的 TTL 缓存解析结果,很多解析器还会根据区域配置缓存“这个域名不存在”的负向响应。即使新记录已经添加,也可能要等这些负向缓存过期,所有人才能看到最新结果。
怎么判断:直接查询几个公共 DNS 解析器,对比返回结果。
最后还有一些不受你控制的部分。搜索引擎抓取过损坏的 URL 后,在重新抓取之前,搜索结果里可能继续显示错误,或者直接移除该页面。聊天应用和社交平台也会缓存链接预览,有时缓存时间还很长,因此页面恢复后,分享出去的链接仍然可能显示错误卡片。很多平台都提供了刷新预览的工具。
curl 请求公开 URL,返回的是修复后的响应吗? Age 和缓存响应头,然后直接请求源站。max-age。针对 4xx 和 5xx 响应,显式设置较短或不允许缓存。最后这个习惯正是 linkbruno.com 在做的事:它从外部检查页面中的链接,走一遍读者访问时经过的所有缓存链路。
你遇到过的链接故障里,哪一层坚持得最久:浏览器、CDN,还是什么更离谱的东西?