Cache-Control里的public、private、no-cache、no-store怎么选?缓存配置与排查指南
修改了网页,用户却仍然看到旧内容;登录后显示了其他用户的信息;接口已经返回no-cache,CDN控制台里却还能看到缓存记录——这些问题经常被归结为“缓存没有清理”,实际原因往往是没有理解Cache-Control各项指令控制的到底是什么。
public、private、no-cache和no-store并不是四档从“缓存最多”到“缓存最少”的开关,它们主要回答三个不同问题:
响应能不能被存储?
可以存到浏览器,还是也能存到CDN、反向代理等共享缓存?
已存储的响应再次使用前,是否必须向源站验证?
最容易记住的判断是:
public:允许浏览器和共享缓存存储;private:只允许用户自己的私有缓存存储,不应进入CDN等共享缓存;no-cache:可以存储,但每次复用前必须向源站验证;no-store:任何缓存都不应存储这次请求或响应。
其中最容易误解的是no-cache。它不是“不缓存”,真正表示禁止存储的是no-store。
先分清浏览器缓存和CDN缓存
一个经过CDN的网站通常至少存在两层HTTP缓存:
源站
│
│ 源站响应与Cache-Control
▼
CDN边缘节点:共享缓存
│
│ CDN返回的响应
▼
用户浏览器:私有缓存浏览器缓存只服务于当前用户,通常被称为私有缓存。CDN、企业代理和反向代理可能同时服务许多用户,属于共享缓存。
这一区别非常重要。某个登录页面允许当前用户的浏览器短暂存储,不代表它可以进入CDN缓存;否则,CDN可能把第一个用户的个性化响应发送给其他用户。
根据《RFC 9111:HTTP Caching》,private禁止共享缓存存储响应,但不禁止私有缓存存储;public则可以明确允许共享缓存存储原本可能不具备共享缓存资格的响应。
四个指令的核心区别
指令 | 浏览器可以存储 | CDN可以存储 | 复用前是否验证 | 常见用途 |
|---|---|---|---|---|
| 可以 | 可以 | 由 | 公共图片、CSS、JS、公开页面 |
| 可以 | 不应存储 | 由 | 用户中心、个性化页面 |
| 可以 | 可以,除非同时有 | 每次复用前必须验证 | 经常变化但可使用ETag验证的内容 |
| 不应存储 | 不应存储 | 无缓存可复用 | 高敏感响应、一次性数据 |
需要注意,public和private主要限制“由谁存储”,no-cache和no-store主要限制“怎样存储及复用”。因此它们可以组合,例如:
Cache-Control: private, no-cache表示浏览器可以保存响应,但每次使用前都要向源站验证;CDN等共享缓存不应保存。
public:允许CDN缓存,但不代表已经设置缓存时间
下面这条响应头明确允许浏览器和共享缓存存储内容:
Cache-Control: public, max-age=3600它表示响应可以被存储,并在接下来3600秒内被视为新鲜内容。在这段时间内,缓存通常可以直接复用,不必访问源站。
public适合所有用户看到相同结果的资源,例如:
不含用户信息的图片、字体、CSS和JavaScript;
公开下载文件;
所有人内容一致的文章页面;
不根据Cookie、认证状态或地区返回不同结果的公开接口。
但只写下面这条通常不够明确:
Cache-Control: publicpublic只是明确授予缓存资格,并没有指定内容可以新鲜多久。缓存可能根据状态码、Last-Modified等信息进行启发式缓存,不同浏览器和CDN的处理也可能不同。生产环境通常应同时给出明确的max-age或s-maxage。
对于文件名带内容哈希、更新时URL一定变化的静态资源,可以使用:
Cache-Control: public, max-age=31536000, immutable例如:
/app.83f7c21a.js
/styles.192b8d44.css一年缓存适用于“内容一变,URL就跟着变”的资源。如果一直使用/app.js这个固定地址,却设置一年浏览器缓存,发布新版本后就很容易出现用户长期拿到旧文件的问题。
public不能用于不受控制的个性化响应
假设下面这个地址会根据登录Cookie返回不同内容:
https://www.example.com/api/profile如果响应被配置为:
Cache-Control: public, max-age=300而CDN缓存键又没有正确区分用户,用户A的资料就可能被缓存后返回给用户B。
不要把“CDN缓存键包含Cookie”当作默认事实。不同CDN和缓存规则对Cookie、查询参数、请求头的处理并不相同,而且把完整Cookie加入缓存键还可能造成缓存碎片和命中率骤降。
对于用户个性化内容,更稳妥的做法通常是使用private或no-store,而不是试图把每一个用户的响应放入公共CDN缓存。
private:禁止共享缓存,不等于浏览器不缓存
典型配置是:
Cache-Control: private, max-age=300它表示响应只能存储在私有缓存中,例如当前用户的浏览器缓存,并可在300秒内直接复用。CDN、共享代理等不应存储该响应。
适合使用private的内容包括:
登录后的用户中心页面;
根据Cookie展示不同状态的HTML;
用户个人偏好或非敏感配置;
仅对当前用户有意义、但允许浏览器短暂复用的数据。
如果内容必须每次确认是否更新,可以使用:
Cache-Control: private, no-cache这并不会完全禁止浏览器保存响应。浏览器仍可以保存内容,但复用前必须向源站验证。
按照MDN的Cache-Control说明,private尤其适合登录后或由Cookie维持会话的个性化响应;如果遗漏该限制,响应有可能进入共享缓存并造成个人信息泄露。
不过,private不是数据安全机制。它依赖缓存遵守HTTP规范,也不会对响应内容进行加密。敏感信息仍然需要HTTPS、可靠的认证授权、会话失效和服务端访问控制。
no-cache:可以存储,但使用前必须验证
下面这条配置经常被误认为“完全关闭缓存”:
Cache-Control: no-cache它真正表达的是:缓存可以存储这个响应,但不能未经源站验证就直接拿来满足后续请求。
验证过程通常依赖以下响应头:
ETag: "page-a81f3"或者:
Last-Modified: Wed, 23 Sep 2026 08:00:00 GMT浏览器或CDN下次请求时可以发送条件请求:
If-None-Match: "page-a81f3"如果内容没有变化,源站返回:
HTTP/1.1 304 Not Modified缓存便可以继续使用原来的响应体,不必重新下载完整内容。如果已经发生变化,源站返回新的200响应和内容。
这就是no-cache的价值:保证使用前经过验证,同时保留304响应带来的传输效率。RFC 9111明确规定,未携带字段参数的no-cache响应在成功完成验证前,不得用于满足其他请求。
适合no-cache的场景包括:
URL固定但内容可能随时更新的HTML;
配置文件、版本清单或应用入口文件;
希望用户每次访问都检查更新,但内容未变化时避免重新传输;
更新频繁、同时已正确设置
ETag或Last-Modified的公开资源。
例如SPA应用可以把入口HTML设置为:
Cache-Control: no-cache
ETag: "index-20260924"HTML每次访问都会验证,HTML引用的带哈希JS和CSS则可以长期缓存。
没有验证器时,no-cache可能无法节省多少流量
如果响应没有ETag或Last-Modified,缓存仍需向源站发起请求,但源站可能只能重新返回完整的200响应。
因此,设置no-cache后应同步检查验证器:
curl -sS -D - -o /dev/null https://www.example.com/index.html重点查看:
Cache-Control
ETag
Last-Modified假设第一次响应包含:
ETag: "index-20260924"可以手动验证条件请求:
curl -sS -D - -o /dev/null \
-H 'If-None-Match: "index-20260924"' \
https://www.example.com/index.html内容未变化时,正常结果通常是304 Not Modified。如果仍返回200,需要检查应用、Web服务器或CDN是否正确处理并转发If-None-Match。
no-store:不允许缓存存储,但不是完整的隐私方案
需要禁止浏览器、CDN和其他HTTP缓存保存响应时,应使用:
Cache-Control: no-store适合的场景包括:
包含高度敏感个人信息的响应;
支付、账户安全或一次性操作结果;
动态生成的密钥、令牌或敏感下载地址;
不应在共享设备本地缓存中留下副本的页面;
明确要求每次都从服务端重新获取的数据。
no-store比no-cache限制更强。前者不应存储,后者可以存储但必须验证。
不过,RFC 9111对no-store的说明同时提醒,它并不是充分的隐私保护措施。恶意或不合规的缓存可能不遵守指令,网络链路也可能面临其他风险。敏感内容仍必须使用HTTPS,并配合服务端认证、授权、会话失效和必要的数据脱敏。
还有一个容易遗漏的问题:把源站响应从public改成no-store,不能保证CDN中已经存在的旧对象立即消失。旧缓存可能在新请求到达源站之前继续被使用。
发生这种情况时,需要同时执行:
在源站改为正确的
Cache-Control;清理CDN中已经存储的对应URL;
必要时更换资源URL或版本号;
检查浏览器是否还保留旧版本;
确认后续响应已经带上新策略。
不同内容应该怎样配置
带哈希的CSS、JS和图片
资源一旦变化就生成新URL:
Cache-Control: public, max-age=31536000, immutable这种方式适合充分利用浏览器和CDN缓存。发布新版本时不要覆盖旧URL,而是让HTML引用新文件名。
URL固定、需要及时更新的HTML
如果每次访问都要确认版本:
Cache-Control: no-cache
ETag: "page-version"如果允许浏览器短暂缓存、CDN缓存时间稍长:
Cache-Control: public, max-age=60, s-maxage=300这里的max-age=60主要控制浏览器新鲜时间,s-maxage=300控制共享缓存。是否完全按照这个策略执行,还要检查CDN规则。
登录后的个性化页面
允许浏览器保存,但禁止CDN保存:
Cache-Control: private, no-cache如果页面包含较敏感信息,或不希望保存在用户设备的HTTP缓存中:
Cache-Control: no-store不要仅因为响应带有Set-Cookie就默认认为CDN一定不会缓存。部分CDN默认会绕过,另一些缓存规则可能覆盖这种行为,必须结合当前厂商配置验证。
公开API
所有用户在相同请求条件下都会得到相同结果,并允许短暂缓存时:
Cache-Control: public, max-age=30, s-maxage=300如果响应会根据Accept-Language或Origin变化,还要正确设计缓存键,并根据实际情况返回Vary:
Vary: Accept-Language不能只加public,却忽略决定响应内容的请求头、查询参数和Cookie。
用户资料和认证接口
普通用户资料通常可以使用:
Cache-Control: private, no-cache令牌、支付结果、密码重置信息等敏感响应通常应使用:
Cache-Control: no-store无论使用哪一种,都不能把缓存指令当成权限校验。未授权用户是否能够取得数据,必须由服务端认证和授权逻辑决定。
public、private能和no-cache、no-store一起写吗?
有些组合是有明确意义的。
public, no-cache
Cache-Control: public, no-cache允许浏览器和共享缓存存储,但每次复用前都要验证。适合公开且需要保持最新、同时支持ETag验证的内容。
private, no-cache
Cache-Control: private, no-cache只允许私有缓存保存,并要求每次复用前验证。适合用户专属但不要求完全禁止本地存储的响应。
private, no-store
Cache-Control: private, no-storeno-store已经禁止所有缓存存储,因此private通常没有增加实质约束。为了让策略简洁清楚,一般直接使用:
Cache-Control: no-storeno-cache, no-store
这也是常见的兼容性写法,但在现代HTTP语义中,既然已经禁止存储,复用前验证就没有多少实际意义。除非需要兼容特定旧系统或框架,通常不必机械叠加。
为什么源站返回no-cache,CDN仍可能缓存
不能只看源站的响应头就断定CDN一定如何处理。CDN控制台中的缓存规则、边缘TTL、最小TTL和强制缓存配置,可能覆盖源站策略。
例如,Cloudflare官方说明中区分了是否启用Origin Cache Control:启用时,no-cache可以存储但每次都重新验证;某些未启用的配置下则会直接不缓存。缓存规则还可以覆盖源站的Cache-Control。
Amazon CloudFront的官方文档也特别警告:如果缓存策略的Minimum TTL大于0,即使源站返回no-cache、no-store或private,CloudFront仍可能至少缓存到Minimum TTL指定的时间。
因此,遇到“响应头明明不允许缓存,CDN却还在命中”时,应按下面顺序检查:
应用生成的Cache-Control
↓
Nginx、Apache或网关是否覆盖/追加响应头
↓
CDN缓存规则是否覆盖源站TTL
↓
CDN缓存键是否包含必要参数
↓
旧缓存是否已经清理
↓
浏览器是否仍保留旧副本不同CDN的命中响应头并不相同。可以先通过CDNChart的CDN检测工具辅助确认当前域名是否经过CDN以及可能使用的服务商,再进入对应厂商控制台检查规则。
用curl检查实际返回的缓存策略
检查公开访问路径时,可以执行:
curl -sS -D - -o /dev/null https://www.example.com/app.js重点查看:
Cache-Control
Age
ETag
Last-Modified
Expires
Vary
Via
X-Cache
CF-Cache-Status
Set-Cookie其中:
Cache-Control表示响应提供的缓存指令;Age通常表示响应在共享缓存中已经存放的秒数;ETag和Last-Modified用于条件验证;Vary说明哪些请求头可能影响缓存版本;X-Cache、CF-Cache-Status等是厂商相关线索,并非所有CDN都会提供;Set-Cookie可能影响缓存资格,但具体行为取决于厂商和规则。
连续请求两次可以观察变化:
curl -sS -D - -o /dev/null https://www.example.com/app.js
curl -sS -D - -o /dev/null https://www.example.com/app.js如果第二次出现HIT或Age增加,通常说明共享缓存正在复用对象。如果始终是MISS、BYPASS或没有Age,可能是响应不可缓存,也可能只是该CDN使用了不同的调试字段。
不要仅依赖curl -I。它发送的是HEAD请求,部分应用或CDN对HEAD与GET的处理不同。上面的-D - -o /dev/null会执行正常GET请求,只是不把响应体输出到终端。
也可以使用CDNChart的网站测速工具观察不同地区公开访问时的状态码和性能表现。不过,公开测速只能辅助发现缓存效果差异,不能代替CDN控制台日志和源站响应头检查。有关平台测试口径,可以参考测评方法与数据说明。
修改缓存配置后怎样确认真正生效
缓存策略变更完成后,不要只刷新一次浏览器。建议至少验证以下内容:
源站直连响应是否已经返回新
Cache-Control;通过CDN访问时,响应头是否被边缘规则改写;
已存在的旧对象是否完成清理;
静态资源第二次请求是否按预期命中;
HTML是否按照设置重新验证或过期;
登录和未登录状态是否返回不同且正确的内容;
两个不同账号是否可能取得相同的个性化缓存对象;
查询参数、Cookie和关键请求头是否正确进入缓存键;
修改内容后,用户是否可以在预期时间内看到新版本。
浏览器开发者工具中的“Disable cache”通常只在开发者工具打开时影响当前浏览器请求,不能代表普通用户的访问状态。强制刷新也可能携带额外的请求缓存指令。因此,排查时应同时测试正常访问、无痕窗口、CDN节点和源站直连结果。
常见问题
no-cache是不是完全不缓存?
不是。no-cache允许缓存存储响应,但要求每次复用前向源站验证。如果需要任何HTTP缓存都不保存,应使用no-store。
private设置后,浏览器还会缓存吗?
可能会。private禁止的是CDN、代理等共享缓存,浏览器等私有缓存仍可保存。实际保存多久还要结合max-age、Expires或no-cache判断。
public不写max-age可以吗?
语法上可以,但缓存新鲜时间可能依赖其他响应头或启发式规则,行为不够明确。生产环境通常应为可缓存内容提供明确的max-age或s-maxage。
max-age=0和no-cache完全一样吗?
两者在很多实际场景中都会导致响应立即过期并触发验证,但语义并不完全相同。max-age=0表示响应立即变旧,no-cache明确要求每次复用前验证。再叠加CDN自身规则后,两者的实际表现还可能不同。
已经设置no-store,为什么用户仍看到旧内容?
旧内容可能在修改响应头之前就已经存入CDN或浏览器。新的no-store响应无法保证旧缓存立即被删除,需要清理CDN缓存、检查浏览器副本,必要时更换资源URL。
public响应带Set-Cookie会不会被CDN缓存?
不一定。许多CDN默认不会缓存带Set-Cookie的响应,但缓存规则可能改变这一行为。不要依赖未经验证的默认设置,更不要对包含用户身份信息的响应强制公共缓存。
使用no-store后,退出登录就一定不能看到旧页面吗?
不能这样保证。HTTP缓存只是浏览器状态的一部分,浏览器还可能使用历史记录或页面快照机制。退出登录时仍需在服务端使会话或令牌失效,敏感接口必须重新校验权限,不能依赖缓存头完成访问控制。
CDN显示HIT,但响应里写着no-cache,是否一定配置错误?
不一定。no-cache允许存储,只是不允许未经验证直接复用。CDN可能已经向源站完成验证后继续使用缓存对象。不过,也需要检查厂商的缓存状态定义、边缘规则和Age变化,确认它是否真的按要求执行了验证。
- Cache-Control public
- Cache-Control private
- no-cache和no-store区别
- CDN缓存设置
- 浏览器缓存控制
- HTTP缓存配置