Cloudflare Cache Rules 实战:把 WordPress 首页 TTFB 压到 90ms

我为什么把 Page Rules 换成了 Cache Rules

三月末给这个站配缓存的时候,Page Rules 面板上多了一行提示:新建规则会走 Cache Rules。我原来那条 pddpdd.cn/wp-content/* 还在跑,没急着动,但顺手点进表达式编辑器看了一眼,就回不去了。

旧方案能写路径通配。新方案能判断 Cookie、请求头、来源国家、设备类型,还能读查询串里有没有某个参数。差的不是功能多少,是有很多逻辑以前要建三条规则互相覆盖才表达得出来,现在一句就够了。

下面是我把 pddpdd.cn 的首页 TTFB 从 460ms 压到 90ms 左右的全部过程。中间把后台和登录态搞坏过两次,也一并写进来。


两套规则到底差在哪

Page Rules 与 Cache Rules 功能对比表
图 1|两者的能力差距主要在匹配方式上

真正的分水岭是匹配能力。Page Rules 只能写 pddpdd.cn/wp-content/* 这种路径通配。Cache Rules 能写完整表达式:

(http.host eq "pddpdd.cn" and not starts_with(http.request.uri.path, "/wp-admin"))

这句话的意思是:只对本站生效,而且把后台路径排除掉。一行说清楚「缓存前台、绕过后台」这件事。

用 Page Rules 表达同样的意思,我当时建了三条:/wp-admin/ 设 Bypass、/wp-content/ 设缓存、其余设缓存。三条规则的先后顺序不能错,后来加了个 /wp-json/* 又要插一条到最前面。这就是我下决心迁移的原因。


一次请求是怎么走的

先把链路摆清楚,不然配规则全靠猜。

Cloudflare 边缘缓存命中流程图
图 2|命中缓存时,源站完全不参与

访客第一次请求,边缘节点没有副本,回源拿到页面存一份。第二次请求同一个 URL,边缘直接返回,源站完全不参与。所以配对的站点,源站 CPU 曲线会平得像条直线。

有个特别容易搞混的地方:回源次数少,不等于 PHP 执行少。如果页面本身是 DYNAMIC,每次都要跑一遍 WordPress,边缘只是每次都老老实实去要。判断有没有真的命中,只看一个响应头:

curl 查看 cf-cache-status 响应头的命令行截图
图 3|HTML 是 DYNAMIC,静态资源是 HIT

cf-cache-status 常见的几个取值:

含义该做什么
HIT命中边缘缓存正常,这就是目标状态
MISS边缘没有,已回源并缓存首次请求,正常
EXPIRED缓存过期,回源更新TTL 大概率设短了
DYNAMIC压根没进缓存HTML 默认是这个,要配规则
BYPASS被规则显式跳过查是不是命中了自己的绕过规则
STALE返回了过期副本源站可能有问题
REVALIDATED304 确认后返回缓存正常

第一次看到首页是 DYNAMIC 的时候我以为是 CF 没生效。其实就是 HTML 默认不进缓存,Cloudflare 一直是这样。


三步配完 Cache Rules

控制台路径:你的域名 → Caching → Cache Rules → Create rule

Cloudflare Cache Rules 三步配置步骤图
图 4|三步顺序建议按这个来

第一步:静态资源长缓存

(http.host eq "pddpdd.cn" and starts_with(http.request.uri.path, "/wp-content/"))
or (http.host eq "pddpdd.cn" and starts_with(http.request.uri.path, "/wp-includes/"))
  • Cache eligibility:Eligible for cache
  • Edge TTL:Ignore cache-control header and use this TTL1 year
  • Browser TTL:同上 → 1 year

图片、CSS、JS 内容一旦生成就不变,而且 WordPress 会带版本号查询串,改版本自然换 URL,长缓存没有副作用。

第二步:HTML 短缓存

WordPress 的 HTML 默认带 Cache-Control: no-cache, must-revalidate,边缘看到这个头就不存。所以要缓存,必须显式覆盖。

(http.host eq "pddpdd.cn"
 and not starts_with(http.request.uri.path, "/wp-admin")
 and not starts_with(http.request.uri.path, "/wp-login.php")
 and not starts_with(http.request.uri.path, "/wp-json")
 and not contains(http.request.uri.path, "preview=true")
 and not any(http.request.cookies["wordpress_logged_in_*"][*] != ""))
  • Cache eligibility:Eligible for cache
  • Edge TTL:Ignore cache-control header and use this TTL4 hours
  • Browser TTL:Ignore cache-control header and use this TTL30 minutes

最后那行 Cookie 判断是全篇最重要的一句。我自己第一次配的时候漏了它,结果登录着后台去看前台文章,看到的是半小时前的旧版本,评论区状态也是乱的,一度以为是主题的 bug。

💡 站上有评论、购物车、会员这类插件的话,Edge TTL 别超过 1 小时,Cookie 判断那行必须保留。

第三步:动态路径显式绕过

(starts_with(http.request.uri.path, "/wp-admin")
 or starts_with(http.request.uri.path, "/wp-login.php")
 or starts_with(http.request.uri.path, "/wp-json")
 or eq(http.request.uri.path, "/xmlrpc.php")
 or starts_with(http.request.uri.path, "/cart"))
  • Cache eligibility:Bypass cache

Bypass cache 和不建规则不是一回事。Bypass 是显式声明,不会因为你后面加了子级规则、或者手滑调了顺序就被意外命中。我当时把 Bypass 规则放在缓存规则后面,结果 /wp-admin 页面被缓存了,登录直接卡在跳转循环里。


缓存键这两个地方得改

Cloudflare 缓存键三个常见误配对照表
图 5|缓存键的三个常见误配

把无意义的查询参数忽略掉

WordPress 会生成 ?ver=6.7.1 这类版本串。不去管它的话,插件一升级,?ver= 一变,缓存键就全换了,等于整站缓存一次性作废。做法是打开 Query String Sort,再加一条忽略名单:

utm_source, utm_medium, utm_campaign, fbclid, gclid, ver

别把 Cookie 塞进缓存键

网上有些教程会让你把所有 Cookie 都纳入缓存键。这个方向是反的。每个访客的 Cookie 都不一样,缓存键永远不重复,命中率会掉到接近零,配了等于没配。Cookie 应该只在规则条件里做判断,不要放进键里。


怎么确认真的配好了

别凭感觉,跑这三条。

验证 Cloudflare 缓存配置的三条 curl 命令
图 6|配完必跑这三条

第一条查首页,第一次 MISSEXPIRED,刷新第二次应该变 HIT。第二条查图片,应该直接 HIT。第三条查后台,/wp-admin 返回 BYPASSDYNAMIC 都对,绝不该是 HIT

如果首页刷两三次还是 MISS,按这个顺序排查:

  1. 表达式里 http.host 的域名写错了。这一条占了我踩坑次数的一半,pddpdd.cn/ 末尾多一个斜杠就匹配不上
  2. 请求带了 Cache-Control: no-cache,而你没勾选忽略源站缓存头
  3. 有插件在输出 Set-Cookie,Cloudflare 见到 Cookie 就跳过缓存
  4. 规则顺序错了,Bypass 排在缓存后面

我实际踩过的五个坑

搜索结果页被缓存了

/?s=关键词 命中缓存之后,A 用户搜到的结果会被 B 用户看到。分页 /page/2/ 同理。在 Bypass 规则里补一句:

(contains(http.request.uri.query, "s=") or contains(http.request.uri.path, "/page/"))

更新文章后前台还是旧的

这是缓存生效的表现,等 Edge TTL 到期就好(4 小时)。急着看的话,登出状态下刷新即可,因为登录态本来就不吃缓存。要立刻全站清就在 Caching → Configuration 里 Purge,但别养成习惯,全站清一次所有访客都要重新回源,小机器扛不住。单篇用 Purge by URL 更稳。

Development Mode 忘了关

这个开关会跳过全部缓存,三小时后自动失效。我在上面浪费过一个晚上,一直在查为什么全部 MISS

没开 Serve Stale

Cache Rules 的 Edge TTL 下面有个 Serve stale content while revalidating。打开之后,缓存过期时访客先拿到旧副本,回源在后台做。页面不用等源站响应,体感差别挺明显。我一开始没开,TTFB 在 90ms 和 300ms 之间来回跳,开了之后就稳住了。

以为它能替代对象缓存

Cache Rules 管的是边缘那一层。源站的 PHP 执行、数据库查询它一概管不到。我这台是 2 核 4G,装了 Redis Object Cache 之后后台的操作响应才顺起来。两件事互补,不是替代关系。


要不要买 APO

Cloudflare 有个付费功能 APO(Automatic Platform Optimization),每月 $5,专门针对 WordPress 优化 HTML 缓存和第三方脚本加载。

我的判断标准:

  • 纯博客:Cache Rules 配好就够,钱可以省
  • 带会员、评论、电商,又不想自己维护规则:APO 值这个价,它会自动处理 Cookie 分层
  • 已经按本文配完并且测速正常:没必要重复付费

先按上面的配一遍,拿 PageSpeed Insights 和 curl 测。首页 TTFB 进 100ms 以内之后,APO 能带来的边际收益已经很小了。我这个站目前的数字是 90ms 上下,就没买。


缓存规则配完之后

这类配置属于「配一次管很久」。我这套规则从三月跑到现在,中间只改过两次,都是因为换了插件。

建议第一次配完就记一组基线:用 PageSpeed Insights 跑一遍首页,把 TTFB、LCP、总请求数抄下来。等哪天换了主题或者加了一堆插件,回头一对比,就知道是哪一步把性能吃掉的。

另外每次 WordPress 升级大版本之后,跑一遍上面那三条验证命令。我遇到过插件升级改写了响应头,把 cf-cache-statusHIT 打回 DYNAMIC 的情况。

如果你还在纠结 VPS 那边的优化,可以接着看 VPS 探针怎么选,那篇讲的是怎么在机器出问题之前先知道。用免费容器搭监控面板的话,claw 免费容器完全指南 里有从注册到保活的完整流程。

上一篇 VPS 探针怎么选?哪吒 / Komari / Uptime-Kuma / Worker 探针横向对比