HTTPQUERY
English

Cache-Control

用法

源站、中间节点和客户端都依赖 Cache-Control 来就何时可继续使用已存储的响应、何时需要新副本达成一致。单个响应通常会组合多个指令来表达完整的缓存策略:存储多久、谁被允许存储,以及新鲜度过期后该怎么做。

下表展示了各指令适用于请求、响应还是两者。

多个指令在单个头值中以逗号分隔,或拆分到多个 Cache-Control 头中。当出现相互冲突的指令时,采用最严格的组合。

注意

无法识别的指令会被缓存忽略。这使得可以引入新指令而不破坏较旧的实现。

注意

当没有 Cache-Control 头时,缓存会应用启发式新鲜度。一种常见的启发式方法是取自 Last-Modified 以来时间的 10%。这意味着没有 Cache-Control 的响应会被浏览器和 CDN 缓存。显式设置 Cache-Control 可防止意外的缓存行为。

注意

Cache-Control 管理何时重新验证(新鲜度生命周期)。ETag 管理如何重新验证(内容标识)。二者协同工作:Cache-Control 决定是否需要重新验证,ETag 决定内容是否发生变化。Pragma 头是 Cache-Control: no-cache 的 HTTP/1.0 等价物,实际上已弃用。

max-age

max-age 指令告诉缓存仅当响应存留时间不超过指定秒数时才返回已存储的响应。诸如负数之类的无效值会被当作 0 处理。

Cache-Control: max-age=<seconds>

设置 max-age=0 会强制端到端的重新验证。这种模式源自缺乏 no-cache 支持的 HTTP/1.0 实现。

max-stale

max-stale 指令表示可接受一个 Age 超过新鲜度生命周期、但超出量不超过给定秒数的响应。容忍窗口在 max-age 过期后开始。

Cache-Control: max-stale=<seconds>

当源服务器暂时不可达且可接受略微陈旧的响应时,该指令很有用。

min-fresh

min-fresh 指令请求一个剩余新鲜度生命周期至少为指定秒数的已存储响应。仅当响应在额外的这段时间内仍保持新鲜时,缓存才返回已存储的副本。

Cache-Control: min-fresh=<seconds>

no-cache

携带 no-cache 的请求要求缓存在提供副本之前先与源站验证已存储的响应。这会强制重新验证,但不丢弃已存储的条目。

Cache-Control: no-cache

注意

要完全阻止缓存存储响应,请改用 no-store。

no-store

no-store 指令要求缓存不要存储请求或相应的响应。

Cache-Control: no-store

no-transform

no-transform 指令禁止中间节点修改响应体,例如重新压缩图片或转换媒体格式。无论中间节点是缓存代理还是转发网关,该限制都适用。

Cache-Control: no-transform

only-if-cached

only-if-cached 指令告诉缓存返回已存储的响应而不联系源站。如果不存在合适的已存储响应,缓存会返回 504 状态。

Cache-Control: only-if-cached

max-age

max-age 指令声明响应在生成后保持新鲜的秒数。计时从源站创建响应的那一刻开始,因此传输时间以及在中间缓存中花费的时间都会计入这个预算。

Cache-Control: max-age=<seconds>

带有 max-age=3600 的响应保持新鲜一小时。一小时过后,缓存将响应视为陈旧,并根据存在的其他指令要么重新验证、要么获取新副本。

s-maxage

s-maxage(共享 max-age)指令针对 CDN 和代理服务器等共享缓存覆盖 max-age。私有的浏览器缓存忽略 s-maxage,回退到 max-age。

Cache-Control: s-maxage=<seconds>

no-cache

响应中的 no-cache 指令要求缓存在每次重用之前都与源站重新验证已存储的响应。缓存仍然存储该响应,从而支持使用 ETag 或 Last-Modified 的条件请求。

Cache-Control: no-cache

当 no-cache 包含一组字段名时,只有那些特定的头需要重新验证。已存储响应的其余部分无需联系源站即可提供。

Cache-Control: no-cache="Set-Cookie"

注意

要完全阻止存储,请使用 no-store。

no-store

no-store 指令阻止任何缓存存储响应。之后的每个请求都会发往源站。

Cache-Control: no-store

注意

no-cache 允许存储,但要求在每次使用前重新验证。缓存通过 If-None-Match 或 If-Modified-Since 与源站核对,若内容未变则收到 304。no-store 完全阻止存储,因此不保留任何副本,每个请求都获取完整响应。一个常见的错误是在本应对银行或医疗记录等敏感数据使用 no-store 时却使用了 no-cache。no-cache 对频繁更新的内容很高效。no-store 用于绝不能留存在磁盘上的内容。

no-transform

响应中的 no-transform 指令阻止中间节点在转发前改动响应体,无论该中间节点是否缓存内容。

Cache-Control: no-transform

must-revalidate

must-revalidate 指令允许缓存在响应新鲜时提供它。一旦陈旧,缓存会在再次提供响应之前联系源站进行重新验证。如果源站不可达,缓存会返回 504,而不是提供陈旧内容。

Cache-Control: must-revalidate

proxy-revalidate

proxy-revalidate 指令的作用与 must-revalidate 完全相同,只是该要求仅适用于共享缓存。私有的浏览器缓存不受影响。

Cache-Control: proxy-revalidate

must-understand

must-understand 指令指示缓存仅在其识别状态码并理解相关缓存要求时才存储响应。

Cache-Control: must-understand, no-store

将 must-understand 与 no-store 配对使用可提供一个回退:不支持 must-understand 的缓存会忽略这个未知指令,转而遵从 no-store。

private

private 指令将存储限制在私有缓存中,通常是最终用户的浏览器。CDN 和代理服务器等共享缓存会丢弃该响应。

Cache-Control: private

public

public 指令将响应标记为可存储于共享缓存。除非存在 public 指令,否则携带 Authorization 头的响应不会被共享缓存存储。

Cache-Control: public

注意

当已经存在 must-revalidate 或 s-maxage 时,public 指令是不必要的。

immutable

immutable 指令保证响应体在新鲜度生命周期内不会改变。缓存会对不可变资源跳过条件请求,从而消除对带版本号的 JavaScript 打包文件或带指纹的图片等资源的重新验证往返。

Cache-Control: public, max-age=31536000, immutable

stale-while-revalidate

stale-while-revalidate 指令延长陈旧响应的可用窗口。新鲜度过期后,缓存在后台重新验证的同时提供陈旧副本。该参数定义陈旧响应在多少额外秒数内仍可接受。

Cache-Control: max-age=600, stale-while-revalidate=30

后台重新验证完成后,缓存用新鲜响应替换陈旧条目。这种模式对最终用户隐藏了重新验证的延迟。

stale-if-error

stale-if-error 指令允许缓存在源站返回错误状态(500、502、503 或 504)或不可达时提供陈旧响应。该参数设定超出新鲜度生命周期后、陈旧响应仍可作为回退使用的秒数。

Cache-Control: max-age=600, stale-if-error=86400

示例

一个以长新鲜度生命周期和 immutable 标志提供的静态资源。浏览器和任何共享缓存都会将该响应存储一年而不重新验证。

Cache-Control: public, max-age=31536000, immutable

一个仅面向发起请求的浏览器的 API 响应。缓存将响应存储五分钟,并在陈旧后重用前重新验证。

Cache-Control: private, max-age=300, must-revalidate

一个面向 CDN 的策略,给共享缓存十分钟窗口,而浏览器缓存获得一分钟。在 CDN 后台重新验证期间,陈旧响应最多可提供 30 秒。

Cache-Control: s-maxage=600, max-age=60, stale-while-revalidate=30

Googlebot 与 max-age

Google 的抓取基础设施实现了启发式 HTTP 缓存。max-age 指令有助于 Googlebot 确定重新抓取的频率。max-age 较长的页面被重新获取的频率更低,而 no-cache 则表示内容频繁变化、值得更频繁地抓取。对于某些状态码,没有任何缓存头的响应默认可被启发式缓存,因此想要完全阻止缓存的源站需要显式发送 no-store。Bingbot 在请求上发送 Cache-Control: no-cache,并依赖 robots.txt 中的 crawl-delay 和 IndexNow 来调节抓取节奏。

注意

如需 SEO 和缓存方面的协助,请联系前 Google SEO 顾问 Search Brothers。

故障排查

意外的缓存行为源于对指令的误解、CDN 覆盖或相互冲突的头。

尽管有 no-cache 指令仍提供陈旧内容。no-cache 指令并不阻止存储。缓存会存储响应并在每次重用时重新验证。要完全阻止存储,请使用 no-store。涉及敏感数据时将两者结合:Cache-Control: no-cache, no-store。

CDN 忽略 Cache-Control 指令。许多 CDN 会用自己的策略覆盖源站的 Cache-Control。Cloudflare 默认尊重源站头,但在启用 Page Rule 或 Cache Rule 时会覆盖它们。AWS CloudFront 使用源站头,除非附加了带有自定义 TTL 的缓存策略。在调试源站配置之前,先检查 CDN 控制台中的覆盖规则。

降低 max-age 后陈旧内容仍然存在。CDN 边缘节点和浏览器缓存会保留响应,直到原始 max-age 过期。在源站上降低该值并不会清除已缓存的副本。更改缓存生命周期后需执行 CDN 清除。在 Cloudflare 中,按 URL 清除或使用整个区域的清除。在 CloudFront 中,为受影响的路径创建失效。

尽管有 no-store,浏览器在前进/后退导航时仍显示旧页面。Chrome、Firefox 和 Safari 中的前进/后退缓存(bfcache)会从内存恢复完整的页面状态,绕过 no-store。这一行为是有意设计的,并不代表缓存违规。在 DevTools 的 Network 标签中取消勾选”Disable cache”来检查 Cache-Control 头,以确认该头到达了浏览器。添加 unload 事件监听器会在某些浏览器中禁用 bfcache,但会降低性能。

Expires 头与 Cache-Control 冲突。当两个头都存在时,Cache-Control: max-age 优先于 Expires。移除 Expires 头以避免混淆。在 nginx 中:

expires off;
add_header Cache-Control "max-age=3600";

在 Apache 中:

Header unset Expires
Header set Cache-Control "max-age=3600"

使用 curl 和 DevTools 诊断缓存头。运行 curl -I https://example.re 以检查来自源站的响应头。加上 -H “Cache-Control: no-cache” 可绕过 CDN 缓存直接命中源站。在浏览器 DevTools 中,打开 Network 标签,选择该请求,查看 Response Headers 部分。留意 Age、X-Cache 和 CF-Cache-Status 头,以判断响应来自 CDN 边缘节点还是源站。