入门指南
purge 返回 200 只说明请求收到了:三层缓存、六个状态值、十分钟核对
改完内容、部署成功,线上还是旧的。purge 的 200 只代表请求被接收,真正要看的是 CF-Cache-Status 与缓存键。把浏览器、边缘、缓存键三层拆开,再给一份十分钟核对顺序。
purge 返回 200 只代表收到,验证要看状态头。

先看答案
- 适合谁
- 改完内容、部署成功,线上看到的还是旧版本的人
- 把 purge 当成「点一下就好」,却说不清它到底做了什么的人
- 用了自定义缓存键或只匹配 GET 的 Cache Rule,purge 总是不生效的人
- 你会获得
- purge 返回 200 只代表收到,验证要看状态头。
- 发布时间
- 实质更新
- 发布
- AOPC 编辑部
60 秒结论
一分钟看懂这篇文章
purge 返回 200 只代表收到,验证要看状态头。
人负责
- 决定清哪一档
- 判断该不该全清
AI 负责
- 核对响应头与字节
- 记录清理前后的状态
- 分层
- 查状态
- 按 URL 清
- 复核
直接答案
「我已经 purge 了,为什么线上还是旧的?」这个问题里藏着四件互相独立的事。任何一件出错都不会报错,而控制台的 purge 按钮只会回你一个 200:
| 层 | 要回答的问题 | 靠什么 | 出错时的表现 |
|---|---|---|---|
| ① 浏览器 | 是我这台机器上的旧副本吗? | 本地 HTTP 缓存 | 无痕窗口正常,同事也正常 |
| ② 边缘 | CDN 缓存里是不是旧的? | 缓存键 + TTL + Cache Rules | CF-Cache-Status: HIT,内容却是旧的 |
| ③ 缓存键 | 「同一个 URL」是不是同一个对象? | 完整 URL(含查询串)+ 请求头 | 清了 A,命中的是 B |
| ④ 证据 | purge 到底做成了什么? | 响应头 + 字节/关键句比对 | purge 返回 200,内容没变 |
最常见的错误是把 ④ 当成「按钮变绿就等于清掉了」。Cloudflare 在自己的文档里把这句话写得不能再直白:
A successful purge request returns HTTP 200. This indicates that Cloudflare received the request — it does not confirm that Cloudflare cached the targeted content or evicted any content. To verify a purge, request the asset after purging and confirm that CF-Cache-Status is no longer HIT.
200 是「收到」,不是「清掉」。 验证动作只有一个:清完之后再请求一次,看 CF-Cache-Status 不再是 HIT。
本文只讨论 Cloudflare 边缘缓存这一层,因为它会给出可远程读取的响应头;浏览器缓存没有服务器侧证据,本文只给排除方法、不给 TTL 数字(编辑判断)。不同 CDN 的产品名不同,但要问的四个问题一样。
一、先分清两个动作:purge 是删,invalidate 是标记过期
控制台和 API 里这两个动作长得很像,结果完全不同:
- purge(清除):把对象从边缘缓存里删掉。下一次请求必然回源,取回最新版本再重新缓存。
- invalidate(失效):不删,只标记为 stale。下一次请求会带条件请求回源校验;源站说「没变」,就继续用缓存里那份。
原文的措辞是「To mark content as stale instead of removing it, invalidate cached content. Cloudflare revalidates invalidated content on the next request and reuses content that has not changed.」
两者的区别在故障时最明显:invalidate 不会让你多一次全量回源,但它也不保证你立刻看到新内容——如果源站(或它前面的另一层缓存)还在返回旧内容,校验会「确认未变」,然后继续用旧的。
还有一条容易漏的成本:invalidation 请求与 purge 请求算同一份额度(原文:Invalidation requests count toward the same limits as purge requests)。所以「先 invalidate 一下试试」不是免费动作。
二、默认值只是起点:HTML 不缓存,robots.txt 会缓存
Cloudflare 的默认缓存规则,按它自己的文档是这样:
Cloudflare only caches based on file extension and not by MIME type. The Cloudflare CDN does not cache HTML or JSON by default. Additionally, by default Cloudflare caches a website’s robots.txt.
拆开看是四条硬规则:
- 按扩展名判断,不按 MIME。
Content-Type: text/html不代表它会被缓存;.css、.js、.png这些在默认扩展名清单里才会。 - HTML 与 JSON 默认不缓存。
robots.txt默认会被缓存——它没有扩展名,却在默认清单里被单独点名。- 不缓存的情形:
Cache-Control为private/no-store/no-cache/max-age=0;响应带Set-Cookie;请求方法不是GET。 缓存的情形:Cache-Control: public且max-age > 0;或Expires是未来时间。同时有max-age与Expires时,用max-age。
没有 Cache-Control 时也不是不缓存,而是按状态码给一个默认 Edge TTL:200/206/301 是 120 分钟,302/303 是 20 分钟,404/410 是 3 分钟,其余状态码默认不缓存。
robots.txt 这一条值得单独记:它默认进缓存,而 RFC 9309 给爬虫的缓存上限是 24 小时(「Crawlers SHOULD NOT use the cached version for more than 24 hours, unless the robots.txt file is unreachable」)。也就是说,你改完 Disallow 之后,边缘缓存与爬虫缓存是两个独立的等待期——清了 CDN 不等于对方立刻重读。
但默认值不是你的 zone 的实际行为
这是本文最想让你带走的一点。本站 2026-10-28 的实测(curl -sSI,非浏览器):
| URL | cf-cache-status |
cache-control |
|---|---|---|
https://aopc.ai/ |
HIT |
public, max-age=0, must-revalidate |
https://aopc.ai/guides/robots-txt-ai-crawler-access-operations/ |
HIT |
同上 |
https://aopc.ai/robots.txt |
HIT |
同上 |
https://aopc.ai/_astro/BaseLayout.DKZMKkD4.css |
HIT |
同上 |
按文档,HTML 默认不缓存;实测本站的 HTML 是 HIT。这说明当前 zone 上还有别的层在决定缓存——可能是一条 Cache Rule,也可能是 Workers 静态资源层,或别的配置。具体是哪一层,本文没有查验该 zone 的配置,不作推断。
结论只有一个:判断缓存状态要看响应头,不要看文档里的默认值。 默认值是排查的起点,不是对你这套配置的承诺。
三、六档粒度:为什么官方推荐 single-file
同一份文档列了这些清除方式,粒度从细到粗:
| 粒度 | 清什么 | 适用场景 |
|---|---|---|
| single-file(按 URL) | 一个 URL 的对象 | 官方推荐,日常发布 |
| cache-tags | 带同一标签的一批对象 | 有打标签的资源 |
| prefix(按 URL 前缀) | 某路径下全部对象 | 整节内容改版 |
| hostname | 某个主机名下的对象 | 换域名/整站切换 |
| purge everything | 整个 zone 的全部缓存 | 最后手段 |
| purge by cache key / varied images / zone versions | 特定缓存键、图片变体、版本 | 高级场景 |
官方对全清的态度不是「慎用」,是明确反对:
Cloudflare strongly recommends using single-file (by URL) purging instead of a complete cache purge.
原因写在 Caution 里:全清会把这个 zone 的缓存一次全部移除,之后每个请求都必须回源,直到缓存重新填满;高流量、资源多的站点会出现回源尖峰,「may significantly slow down your site or overload your origin server」;如果用了 Tiered Cache,重新填充还会跨缓存层传播,进一步放大回源压力。
还有一个多环境陷阱:生产环境的 purge everything 会跨所有环境清空,非生产环境只清它自己那一层。也就是说,你在预览环境点「全清」,生产不受影响;你在生产点,所有环境一起重来。
两种清法的结果状态也不同(都很好记):
- single-file 之后,后续请求
CF-Cache-Status: MISS; - purge everything 之后,每个资源的下一次请求也是
MISS。
所以「清完还是 HIT」基本可以断定:你清的不是那个缓存键(见第五节),而不是「purge 没生效」。
四、限额按账号算,不按站点算
这是最容易被误读的一节。文档写得很清楚:The following limits apply per account to purge and invalidation requests。
| 项 | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| 请求速率(hostname/tag/prefix/全清) | 5 请求/分钟 | 5 请求/秒 | 10 请求/秒 | 50 请求/秒 |
| 令牌桶容量 | 25 | 25 | 50 | 500 |
| 每次请求最多操作数 | 100 | 100 | 100 | 100 |
| single-file URL 速率 | 800 URL/秒 | 1 500 URL/秒 | 1 500 URL/秒 | 3 000 URL/秒 |
| single-file 每次最多操作数 | 100 | 100 | 100 | 500 |
三条推论,都是一人公司会直接踩到的:
- 同一档位的多个 zone 共享额度。原文举例:账号里所有 Pro 计划的 zone 共用 Pro 那一档的限额。你有三个站,就是三个站抢每分钟 5 次(Free)。
- 它是令牌桶,不是硬性计数。桶里攒着令牌时可以短时爆发(Free 最多 25 个),桶空了就要等新令牌。所以「刚才还能清,现在报错」可能只是桶空了。
- URL 阈值按移动平均计算,不是「这一秒塞 800 个就一定过」。
顺带说一个心理陷阱:限额是给「操作」的,不是给「正确」的。清错 URL 也照样消耗额度,而且不会告诉你清错了。
五、single-file purge 返回 200 却没清掉:五个原因
这一节是全文最实用的部分。官方在 single-file 页面里列了一串「什么时候它清不掉」,每一条都能解释一次「我明明清了」。
① 自定义缓存键:控制台发不出那些头
如果你用 Cache Rule 设了包含 header/cookie 的自定义缓存键,dashboard 的单文件清除不会生效——因为 dashboard 无法把那些值放进 purge 请求。例外是设备类型与国家(Enterprise 可在按 URL 清除时设置)。
正确做法:用 API 按 URL 清除,并在请求体的 headers 对象里带上缓存键用到的头与 cookie。原文还有一句很关键的:purge 请求里缺了某个头,Cloudflare 会把它当作空值——于是你以为清了 A,实际清的是 A + 空头 这个不存在的键。
只改查询串处理的自定义键(例如忽略查询串)一般还能被 dashboard 覆盖;其它情况不要赌。
② Cache Rule 只匹配 GET:purge 请求的方法不是 GET
单文件清除时,Cloudflare 用 PURGE 方法发起内部请求。如果你的规则写成只匹配 GET:
(http.host eq "example.com" and http.request.method eq "GET")
那么 purge 请求根本不匹配这条规则,也就清不到由它缓存的对象。文档给的修法是让表达式同时匹配两种方法:
(http.host eq "example.com" and (http.request.method eq "GET" or http.request.method eq "PURGE"))
对无法在 purge 时求值的字段(例如 cf.bot_management.score),只能改用 prefix、tag 或全清。
③ URL 是 301/302:你清掉的是跳转,不是目标页
这一条对静态站尤其致命。原文:如果 URL 返回 301 或 302,单文件清除只移除被缓存的跳转响应,不清除跳转目标的内容。
Astro 默认的 trailingSlash 行为就会制造这种跳转:/foo → /foo/。如果你清的是 /foo,被清掉的是那条 301,而 /foo/ 的正文还在缓存里。
文档给的定位命令很直接:
curl -Ls -o /dev/null -w "%{url_effective}\n" https://example.com/redirecting-path
拿输出的最终 URL 去清。顺带记住:single-file 的 host 部分会被转小写,path 部分区分大小写(/helloHI ≠ /hellohi)。
④ 特殊请求头:dashboard 清不掉带这些头的对象
带 Origin 头,或带 X-Forwarded-Host、X-Host、X-Forwarded-Scheme、X-Original-URL、X-Rewrite-URL、Forwarded 的对象,dashboard 的单文件清除不会清。默认缓存键本身就包含客户端发来的 Origin 头(为 CORS 支持),所以跨域请求过的资源可能有两个键。这类对象要用 API,并在 headers 里带上匹配的值。
⑤ 通配符、编码与 Transform Rule
- 单文件清除不支持通配符,也不支持「清一个目录」;要按路径批量清,用 prefix、hostname 或 cache tags。
- URL 必须是 UTF-8 编码。
- 如果用了 Transform Rule 改写路径,purge 要用改写前的(终端用户看到的)URL,否则清不到。
把五条压成一句:purge 的入参不是「一个页面」,是「一个缓存键」。 键对不上,200 也照样返回。
六、六个状态值各说明什么
CF-Cache-Status 是这套系统里唯一能远程读到的「实话」。六个最常用的值:
| 值 | 含义 | 典型场景 |
|---|---|---|
HIT |
命中边缘缓存 | 正常命中;也可能是你清错了键 |
MISS |
有资格缓存,但请求时不在缓存里,已回源 | purge 后的第一次请求 |
EXPIRED |
在缓存里但已过期,回源取了新的 | TTL 到期 |
REVALIDATED |
源站通过条件请求确认未变,继续用缓存副本 | no-cache/max-age=0 的校验 |
BYPASS |
请求时判定有资格,但源站响应最终不可缓存 | no-store/private、Set-Cookie、超出体积上限 |
DYNAMIC |
请求时就不具备缓存资格 | 未被规则纳入的 HTML、非 GET 等 |
三个最容易混的边界:
MISS≠BYPASS。原文:Responses that Cloudflare chooses not to cache return BYPASS instead of MISS.MISS是「该存但当时没有」,BYPASS是「本来可以存,但源站说不」。BYPASS≠DYNAMIC。BYPASS的决定发生在响应时(origin response 或响应头让别存);DYNAMIC的决定发生在请求时(这个资源压根不在缓存范围内)。文档专门提醒:给 HTML 配了「Eligible for cache = Yes」,但源站返回no-store,结果是BYPASS而不是DYNAMIC。no-store与no-cache不是一回事。no-store/private产生BYPASS;而no-cache、max-age=0、s-maxage=0在 Origin Cache Control 开启时不产生 BYPASS,而是首次MISS、之后REVALIDATED或EXPIRED。
Age 头能补一刀:它是对象进入缓存(或被重新校验)以来的秒数,在重新校验、purge、驱逐时归零。文档还说明它只在 HIT、STALE、UPDATING 上设置,MISS、EXPIRED、BYPASS、DYNAMIC、NONE/UNKNOWN 上不设。
补充一句实测边界:本站的 HIT 响应里没有 Age 头。本文不推断原因(可能与上面的 zone 配置差异同源),但它说明一件事——不要用单个头下结论,CF-Cache-Status 与 Age、ETag、响应体关键句要一起看。
另外还有 STALE(缓存过期且联系不上源站,返回旧副本)、UPDATING(异步 stale-while-revalidate 正在后台更新)、NONE/UNKNOWN(Worker/WAF/重定向在缓存之前就生成了响应)。看到 STALE 不是「缓存坏了」,是源站当时不可达。
七、改完内容十分钟内的四个检查点
顺序很重要:先证明「部署换了文件」,再证明「缓存换了对象」,最后才怀疑浏览器。
| 时间 | 看哪里 | 动作 | 通过标准 |
|---|---|---|---|
| 0–1 分钟 | 部署是否真的换了文件 | curl -sS <url> | grep -c '<新标题里的词>',与本地 dist/ 对照 |
关键句命中,且与本地构建产物一致 |
| 1–3 分钟 | 边缘是否换了对象 | curl -sSI <url> 看 cf-cache-status(必要时再 GET 一次) |
不再是 HIT;全清后应是 MISS |
| 3–6 分钟 | 同一批受影响的 URL | 列表页、首页、RSS/sitemap、带查询串的变体各查一条 | 每一条都换掉,而不是只有你清的那条 |
| 6–10 分钟 | 浏览器这一层 | 无痕窗口,或开发者工具勾选 Disable cache | 与 curl 结果一致 |
第 1 步为什么放最前:如果线上和本地 dist/ 一致,问题根本不在缓存,继续 purge 只是白烧额度。第 3 步为什么不能省:single-file 只清一个键,列表页与首页是另外的键;带 ?utm_source=... 的 URL 也是另外的键——默认缓存键包含带查询串的完整 URI(原文:URI with query string)。
一张「看到什么 → 下一步」的对照表:
| 现象 | 最可能的原因 | 下一步 |
|---|---|---|
purge 200,仍 HIT,内容旧 |
清的是另一个键(/foo vs /foo/、查询串变体、自定义键) |
先 curl -Ls -w "%{url_effective}" 拿最终 URL,再按原样(含查询串)清 |
purge 200,变 MISS,内容仍旧 |
源站或构建产物没换,缓存背了锅 | 比对 dist/ 与部署版本,回到发布链 |
一直 BYPASS |
源站响应不可缓存(no-store/private、Set-Cookie、体积超限) |
看响应头,改源站或 Cache Rule |
一直 DYNAMIC |
请求时就不具备缓存资格 | 检查规则是否覆盖该路径/扩展名 |
curl 是新的,浏览器是旧的 |
浏览器本地缓存 | 无痕/禁用缓存排除(编辑判断) |
STALE |
源站当时不可达 | 先查源站健康,不是先清缓存 |
八、五种常见误读
- 「purge 返回 200 = 已经清掉了。」 原文只说「收到请求」,并明确它不确认「缓存过」也不确认「驱逐了」。验证看
CF-Cache-Status。 - 「HTML 默认不缓存,所以改 HTML 不用管缓存。」 默认值不是当前配置。本站 HTML 实测就是
HIT。先看头,再决定要不要清。 - 「purge everything 最彻底,所以最安全。」 官方明确「strongly recommends」单文件;全清让后续请求全部回源,可能压垮源站,Tiered Cache 还会跨层重填,生产环境更是跨所有环境。
- 「改了
Cache-Control就会改变边缘 TTL。」 边缘优先看Cloudflare-CDN-Cache-Control,其次CDN-Cache-Control,最后才是Cache-Control;Edge Cache TTL规则又会覆盖前两者;响应里出现Surrogate-Control时,Cloudflare 直接忽略Cache-Control。 - 「清一个 URL 就够了。」 列表页、首页、RSS、带查询串的变体都是不同的键;
/foo与/foo/之间还隔着一条 301。
九、本周就能做的五件事
- 把
curl -sSI写进发布检查单:至少查三条——新页面、列表页、一个静态资源,把cf-cache-status抄进当次发布记录。 - purge 之前先跑一次最终 URL 定位:
curl -Ls -o /dev/null -w "%{url_effective}",确认你要清的到底是哪条 URL,尤其是带尾斜杠的静态站。 - 翻一遍 Cache Rules 的表达式:凡是只匹配
GET的,加上PURGE;否则单文件清除永远打不中。 - 有自定义缓存键的,purge 改走 API:把 header/cookie 一起带上;缺一个头会被当成空值,等于清了个不存在的键。
- 给「全清」加一道人工闸:默认只做 single-file;要全清时,先写下影响范围(同账号同档位的其它 zone、所有环境、Tiered Cache 重填),再决定。
如果只能记住一件事:purge 的入参是一个缓存键,不是一篇文章;返回的 200 说的是「收到了」,不是「清掉了」。
边界说明:本文的实现细节全部来自 Cloudflare Cache 的六个公开文档页与 RFC 9309(均于 2026-10-28 访问、HTTP 200,字节数见来源节)。不写「purge 成功=已清除」、「HTML 默认会被缓存」、「按 MIME 判断」、「限额按站点算」,也不写任何 TTFB 提升、回源下降、命中率之类的数字。文中标注为编辑判断的有三处:浏览器缓存只给排除方法不给 TTL;Age 头缺失不作原因推断;「十分钟」是操作节奏建议,不是产品承诺。第二节的本站四条响应头为 2026-10-28 当日 curl -sSI 实测结果;本站 HTML 为何是 HIT,本文未查验 zone 配置,故只陈述现象、不归因。限额与状态值只描述 Cloudflare 的实现,不代表其它 CDN 的口径。
来源
以下页面均于 2026-10-28 逐条访问并验证为 HTTP 200;字节数为当日抓取的实际大小。
- Purge cache核查于 2026/10/28(200,121 466 B;Last updated Sep 29, 2026):
HTTP 200只表示 Cloudflare 收到请求、不确认缓存过或驱逐过,验证方式是请求后确认CF-Cache-Status不再是HIT;invalidate 是「mark content as stale instead of removing it」,下次请求回源校验、未变则复用;invalidation 与 purge 共用限额;限额按账号——Free 5 请求/分钟、Pro 5/秒、Business 10/秒、Enterprise 50/秒,令牌桶 25/25/50/500,每次最多 100 个操作(Enterprise 单文件为 500);单文件 URL 速率 800/1 500/1 500/3 000,按移动平均计算;同档位 zone 共享限额。 - Default Cache Behavior核查于 2026/10/28(200,131 443 B;Last updated Sep 14, 2026):
Cloudflare only caches based on file extension and not by MIME type、does not cache HTML or JSON by default、by default Cloudflare caches a website's robots.txt;不缓存四条件(private/no-store/no-cache/max-age=0、Set-Cookie、非 GET);缓存两条件(public且max-age>0、Expires为未来);无Cache-Control时按状态码,默认 TTL 为 200/206/301 → 120m、302/303 → 20m、404/410 → 3m,其余不缓存;max-age优先于Expires;同机房并发未命中用 cache lock 只回源一次。 - Purge everything核查于 2026/10/28(200,115 778 B;Last updated Sep 29, 2026):
strongly recommends using single-file (by URL) purging instead of a complete cache purge;全清会移除 zone 全部缓存、后续请求全部回源,高流量站可能significantly slow down your site or overload your origin server;Tiered Cache 会在层间重新填充;生产环境全清跨所有环境,非生产只清该环境;全清后下一次请求CF-Cache-Status为MISS。 - Purge by single-file核查于 2026/10/28(200,131 328 B;Last updated Sep 29, 2026):host 转小写、path 区分大小写;自定义缓存键(含 header/cookie)时 dashboard 清不掉,需用 API 并在
headers里带值、缺头按空值处理(设备类型与国家为 Enterprise 例外);只匹配 GET 的 Cache Rule 不匹配 purge(内部用PURGE),表达式需同时匹配两种方法;301/302 只清跳转不清目标,附curl -Ls -o /dev/null -w "%{url_effective}";带Origin、X-Forwarded-Host、X-Host、X-Forwarded-Scheme、X-Original-URL、X-Rewrite-URL、Forwarded的对象 dashboard 清不掉;不支持通配符、须 UTF-8、Transform Rule 场景用改写前 URL;单文件清除后为MISS。 - Cloudflare cache responses核查于 2026/10/28(200,127 607 B;Last updated Sep 4, 2026):
CF-Cache-Status各值定义——MISS为「可缓存但请求时不在缓存」,BYPASS为「请求时有资格、响应最终不可缓存」且Responses that Cloudflare chooses not to cache return BYPASS instead of MISS,DYNAMIC仅在请求时判定不具备资格时返回;no-store/private产生BYPASS,no-cache/max-age=0/s-maxage=0产生MISS后REVALIDATED或EXPIRED;Age在重新校验、purge、驱逐时归零,只在HIT/STALE/UPDATING上设置;STALE为缓存过期且联系不上源站;NONE/UNKNOWN为 Worker/WAF/重定向在缓存前生成响应。 - Cache Keys核查于 2026/10/28(200,143 751 B):默认缓存键包含
Full URL(scheme、host、URI with query string,示例/logo.jpg?utm_source=newsletter)、客户端Origin头(CORS 支持)、x-http-method-override/x-http-method/x-method-override、x-forwarded-host/x-host/x-forwarded-scheme等;Ignore Query String会去掉 URI 里的查询串;查询串可按参数 include/exclude。 - CDN-Cache-Control核查于 2026/10/28(200,123 434 B;Last updated Apr 16, 2026):
CDN-Cache-Control与Cloudflare-CDN-Cache-Control用与Cache-Control相同的指令;同时返回两者时 Cloudflare 只看Cloudflare-CDN-Cache-Control;Edge Cache TTL规则会覆盖这两个头;响应出现Surrogate-Control时忽略Cache-Control。 - RFC 9309: Robots Exclusion Protocol核查于 2026/10/28(200,25 265 B,纯文本;IETF Standards Track,2022-09):2.4 Caching ——
Crawlers MAY cache the fetched robots.txt file's contents、SHOULD NOT use the cached version for more than 24 hours, unless the robots.txt file is unreachable。