
CDN 缓存过期并不一定意味着用户必须停下来等待源站。通过 HTTP Cache-Control 中的 stale-while-revalidate 和 stale-if-error 指令,CloudFront 可以在特定时间窗口内继续使用已经过期的缓存:前者用于后台更新期间降低等待时间,后者用于源站故障时维持内容可访问性。
这两个指令解决的问题不同。stale-while-revalidate 偏向性能,让用户先收到旧内容、CDN 再异步更新;stale-if-error 偏向可用性,只在回源失败或源站返回特定服务器错误时使用旧内容。配置时不能只填写一个很大的秒数,还需要同时考虑内容新鲜度、CloudFront 最大 TTL、错误缓存、自定义错误页面以及对象是否仍留在边缘缓存中。
CDN 缓存从“新鲜”变成“过期”后会发生什么?
在没有过期内容策略时,一个对象超过 max-age 或 s-maxage 后,下一次请求通常会触发重新验证。CloudFront 向源站发起请求;如果对象没有变化,源站可以返回 304 Not Modified,CDN 继续使用原缓存;如果对象已经变化,源站返回 200 OK 和新内容。
问题在于,触发重新验证的用户可能需要等待源站响应。如果源站很慢、正在扩容或暂时不可用,等待时间会进一步增加,甚至直接收到 5xx 错误。过期内容指令允许 CDN 在受控时间内把旧对象当作临时缓冲层。
| 阶段 | 对象状态 | 典型处理 |
|---|---|---|
| 新鲜期 | 仍在 max-age 或 s-maxage 内 | 直接从缓存返回,不需要回源验证 |
| SWR 窗口 | 已经过期,但仍在 stale-while-revalidate 允许范围内 | 先返回旧内容,同时在后台重新验证或获取新版本 |
| SIE 窗口 | 缓存已过期,且回源不可达或返回适用的 5xx 错误 | 以旧内容代替源站错误 |
| 所有允许窗口结束 | 旧内容不再符合服务条件 | 等待回源结果,或返回错误与自定义错误页面 |
stale-while-revalidate 是什么?
stale-while-revalidate 常简称为 SWR。它允许 CloudFront 在缓存过期后先把旧内容返回给用户,同时异步向源站检查或获取新版本。这样,触发更新的请求也不必承担完整的回源等待时间。
Cache-Control: public, max-age=3600, stale-while-revalidate=600
以上配置可以理解为:
对象在前 3,600 秒内属于新鲜缓存。
超过 3,600 秒后,如果出现新请求,CloudFront 可以立即返回旧版本并在后台更新。
SWR 允许使用旧版本的时间为 600 秒,但实际可用时间还会受 CloudFront 最大 TTL 限制。
后台更新成功后,后续请求收到新内容,并从新的缓存时间重新计算。
SWR 特别适合新闻列表、产品目录、博客页面、公共 API 查询结果和配置清单等“需要定期更新,但短时间看到上一版本不会造成严重后果”的内容。它不适合余额、库存扣减结果、一次性验证码、权限状态、结算价格等必须实时准确的数据。
stale-if-error 是什么?
stale-if-error 常简称为 SIE。当源站不可访问,或 CloudFront 回源时遇到 500 至 599 范围内的错误,CloudFront 可以在指定窗口内返回缓存中的旧版本。它的目标不是让更新更快,而是在源站故障期间提供降级内容。
Cache-Control: public, max-age=3600, stale-if-error=86400
这个例子表示对象正常缓存 1 小时。如果缓存过期后源站发生适用的错误,CloudFront 最多可在指定的 86,400 秒窗口内使用旧内容。但这不是“源站宕机后一定能继续访问 24 小时”的承诺,因为旧对象必须仍然存在于相应边缘缓存中,而且 CloudFront 最大 TTL 不能提前截断该窗口。
SIE 不是备份系统。边缘节点可能因为对象不常访问或缓存空间管理而提前移除对象。关键业务仍应配置健康检查、多源站、源站故障转移、数据库容灾和可恢复的发布流程。
两个指令能否同时使用?
可以,而且两者组合通常更完整:
Cache-Control: public, max-age=3600, stale-while-revalidate=600, stale-if-error=86400
这条响应头建立了三层行为:
前 1 小时:直接使用新鲜缓存。
缓存过期后的更新阶段:在最多 10 分钟的 SWR 窗口内先返回旧内容,同时后台更新。
如果更新时源站不可达或返回适用的服务器错误:在 SIE 允许范围内继续返回旧内容。
两个窗口是独立指令,不能把它们简单理解为必然连续相加的“1 小时 + 10 分钟 + 24 小时”。实际处理还取决于请求发生时间、回源结果、对象是否仍在缓存,以及 CloudFront 缓存策略中的最大 TTL。
max-age、s-maxage 与 CloudFront TTL 如何配合?
max-age 同时可能影响浏览器与共享缓存;s-maxage 专门用于 CDN 等共享缓存,并在存在时优先决定 CloudFront 的新鲜期。需要让浏览器较快检查更新、但允许 CDN 边缘缓存更久时,可以分别设置:
Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=600, stale-if-error=86400
在这个例子中,浏览器的新鲜期为 60 秒,共享 CDN 缓存的新鲜期为 3,600 秒。SWR 和 SIE 描述的是新鲜期结束后可使用旧响应的条件。
CloudFront 缓存策略中的 Minimum TTL、Default TTL 和 Maximum TTL 会与源站响应头共同决定实际行为,尤其要注意:
Maximum TTL 会限制旧内容窗口。CloudFront 使用旧内容的时间不会超过相应过期指令与最大 TTL 中较小的值。
Minimum TTL 大于 0 时要格外谨慎。即使源站返回
no-cache、no-store或private,CloudFront 仍可能按照最小 TTL 缓存响应。需要明确禁止错误时返回旧内容,可设置
stale-if-error=0。这对认证结果、敏感状态或必须失败关闭的接口更安全。没有源站缓存头时,Default TTL 才会发挥主要作用。不要一边依赖默认值,一边假设所有路径都具有相同的新鲜度要求。
CloudFront 错误缓存与 stale-if-error 有什么区别?
两者很容易混淆,但返回给用户的内容不同:
| 机制 | 缓存或返回什么 | 主要目的 |
|---|---|---|
stale-if-error | 之前成功获取、现在已经过期的正常内容 | 源站故障时继续提供可用页面或资源 |
| Error Caching Minimum TTL | 源站返回的 4xx/5xx 错误响应或配置的自定义错误页面 | 减少重复回源,防止故障期间大量请求持续冲击源站 |
| 自定义错误响应 | 预先配置的错误页面,也可以修改向查看器返回的状态码 | 在没有可用旧内容时提供更友好的失败体验 |
CloudFront 默认会将错误响应缓存 10 秒,也可以针对不同状态码配置 Error Caching Minimum TTL。该值太短会增加故障期间的回源压力;太长则可能让用户在源站恢复后继续看到旧错误。配置了 SIE 和自定义错误页面时,CloudFront 会先尝试返回符合 SIE 条件的旧内容;旧内容不可用时,再使用对应的自定义错误响应。
哪些内容适合提供过期版本?
| 内容类型 | 建议 | 原因 |
|---|---|---|
| 静态文章、帮助中心、产品说明 | 通常适合同时使用 SWR 与 SIE | 短时间旧版本的风险较低,且可减轻发布和故障期间的源站压力 |
| 商品列表、公共目录 | 可使用较短 SWR,并谨慎设置 SIE | 需要在速度与库存、价格新鲜度之间平衡 |
| 公共配置、版本清单 | 根据客户端兼容要求决定 | 旧配置可能保持服务,也可能阻止紧急修复生效 |
| 登录态、权限和账户资料 | 通常不应由共享 CDN 缓存 | 存在隐私泄露和权限状态过期风险 |
| 支付、余额、订单提交结果 | 不建议使用旧内容兜底 | 错误的业务状态可能比直接返回错误更危险 |
| 安全公告、撤回内容 | 应缩短或禁用旧内容窗口 | 旧版本可能延迟重要修正或内容下架 |
源站如何添加 Cache-Control?
Nginx 示例
location /articles/ {
add_header Cache-Control "public, max-age=300, s-maxage=3600, stale-while-revalidate=600, stale-if-error=86400" always;
}Apache 示例
<Location "/articles/"> Header always set Cache-Control "public, max-age=300, s-maxage=3600, stale-while-revalidate=600, stale-if-error=86400" </Location>
应用响应示例
Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=600, stale-if-error=86400 ETag: "article-20260817-v3" Last-Modified: Mon, 17 Aug 2026 03:20:00 GMT
不要不分路径地给整站添加相同响应头。静态文章、用户中心、API、下载文件和错误响应具有不同风险,应按缓存行为或路由分别配置。对于带 Cookie、Authorization 或用户身份信息的页面,必须先确认不会把一个用户的响应共享给其他用户。
如何验证配置是否生效?
1. 检查源站和 CDN 响应头
curl -I https://www.example.com/articles/test-page
确认最终响应中包含预期的 Cache-Control,并记录 Age、ETag、Last-Modified、Via 和 CloudFront 缓存状态响应头。不要只检查源站地址,因为中间代理或响应头策略可能改写结果。
2. 观察新鲜期与过期后的行为
测试环境可以使用较短时间,例如:
Cache-Control: public, max-age=20, stale-while-revalidate=30, stale-if-error=120
先连续请求建立缓存,再等待新鲜期结束。修改源站对象并立即请求 CDN,观察第一次请求是否仍快速返回旧版本,以及随后请求是否切换到新版本。
3. 模拟源站故障
只在测试分配或可控环境中进行。先确保边缘节点已有成功响应的缓存,再让测试源站返回 503 或暂时不可访问,检查 CDN 是否返回旧内容。随后恢复源站,验证缓存能够重新更新,而不是长期停留在旧版本。
4. 从多个区域重复验证
CloudFront 缓存分布在不同边缘站点。某个测试位置存在旧对象,不代表所有地区都已经缓存该对象。因此,SIE 不能替代主动预热、源站冗余和跨区域监控。
常见配置错误
把 SWR 当成定时主动刷新
SWR 通常仍需要缓存过期后的请求触发重新验证。低访问量对象可能在过期后很久都没有请求,也可能在缓存空间管理中被提前移除。需要严格定时更新时,应使用发布流程、预热或主动请求配合。
把 SIE 当成所有错误的通用兜底
CloudFront 文档描述的 SIE 场景是源站不可达或源站返回 500 至 599 范围内的错误。业务主动返回的 401、403 或 404 不应被假定会自动使用旧成功内容;这些状态通常代表权限、资源存在性或请求本身的问题。
只设置响应头,不检查 Maximum TTL
如果 CloudFront 最大 TTL 小于期望的旧内容窗口,过大的 SWR 或 SIE 数值不会完整生效。应把源站头部与对应缓存行为的最小、默认和最大 TTL 放在同一张配置表中审核。
给实时业务使用超长旧内容
旧商品价格、库存、权限或安全状态可能造成业务和合规风险。可用性不是越高越好;当旧数据可能产生更严重后果时,清晰失败通常比返回成功但错误的内容更安全。
更新或撤回内容后忘记旧缓存窗口
如果内容必须立即下线,不应等待 TTL、SWR 或 SIE 自然结束。应执行 CloudFront 缓存失效,并确认所有相关 URL、查询参数变体和语言版本均被覆盖。
推荐的配置思路
先按内容风险分类。明确哪些页面允许短时间旧版本,哪些页面必须始终从源站或私有缓存获取。
分别设置浏览器与 CDN 新鲜期。使用
max-age和s-maxage控制不同层级,而不是让所有缓存共享一个期限。让 SWR 保持较短。它主要覆盖正常后台更新所需时间,不应用来隐藏长期发布失败。
让 SIE 对应真实恢复目标。结合源站平均修复时间、内容陈旧风险和故障转移能力确定窗口。
检查 CloudFront Maximum TTL。确保它不会意外截断计划中的过期内容窗口。
同时配置错误缓存。避免源站故障时因错误缓存太短而产生回源请求风暴。
为紧急下线保留失效流程。过期内容策略必须能被人工或自动化发布流程快速撤销。
监控内容版本。日志中记录或关联 ETag、缓存状态、Age、源站状态码与响应时间,判断用户实际收到哪个版本。
常见问题
缓存过期后,CloudFront 一定会返回旧内容吗?
不一定。必须满足指令、时间窗口、错误条件和 CloudFront 缓存策略等要求,而且对象需要仍然存在于相应边缘缓存中。对象可能因为低访问量被提前移除。
stale-while-revalidate 会不会让所有用户一直看到旧内容?
正常情况下不会。它在返回旧内容的同时触发后台重新验证,更新成功后后续请求会使用新版本。但如果源站更新持续失败,实际行为还会受到 SIE、最大 TTL 和错误处理配置影响。
stale-if-error 可以代替多源站故障转移吗?
不能。SIE 只能返回此前缓存过的旧响应,不保证每个边缘节点都有副本,也无法处理必须实时执行的动态请求。它适合作为故障转移之外的一层缓冲。
s-maxage 和 max-age 应该同时设置吗?
如果希望浏览器和 CDN 使用不同的新鲜期,建议同时设置。CloudFront 作为共享缓存会优先使用 s-maxage,浏览器通常依据 max-age。
如何完全禁止 CloudFront 在源站错误时提供旧对象?
根据 AWS 文档,可在响应中设置 stale-if-error=0。同时还应检查缓存策略中的最小 TTL、错误缓存和自定义错误响应,确保整体行为符合“失败关闭”的要求。
结论
stale-while-revalidate 和 stale-if-error 都允许 CDN 使用过期缓存,但目标不同:SWR 用旧内容换取更低的更新等待时间,SIE 用旧内容换取源站故障期间的可用性。两者可以组合使用,却不能脱离 max-age、s-maxage、CloudFront Maximum TTL 和错误缓存单独理解。
一套稳妥的配置应该从内容风险出发:静态文章和公共目录可以设置合理的旧内容窗口;账户、权限、支付和实时状态则应谨慎缓存或明确设置 stale-if-error=0。上线前应测试正常过期、后台更新、源站 5xx、源站不可达、紧急失效和多区域缓存差异,确保“继续访问”不会变成“长期返回错误版本”。
参考资料
AWS CloudFront 开发者指南:管理内容保留在缓存中的时间长度(过期),核查日期:2026-08-17
AWS CloudFront 开发者指南:控制 CloudFront 缓存错误的时间长度,核查日期:2026-08-17
IETF RFC 5861:HTTP Cache-Control Extensions for Stale Content,核查日期:2026-08-17
MDN:Cache-Control,核查日期:2026-08-17