Contents
1 前言
这周原本计划写的文章不是这篇,但在由于在研究 “APO对 WordPress 内容缓存的影响”时,无意间发现了一个异常的情况:访问博客请求的响应标头中出现了 “Cf-Cache-Status: BYPASS“:

最关键的是,不管怎么刷新都是”BYPASS”,这意味着APO对我博客的访问请求处于失效的状态(不知道问题的实际影响范围,只知道至少对我的访问请求是如此)。这让我感到非常不爽,毕竟现在已经是高贵的Cloudflare Pro用户,而APO功能可是单独拿出来卖5美金一个月的昂贵服务,怎么能不生效呢?这事必须得重视!
为了弄清楚背后的原因,我不得不中途调整方向,深入研究这一现象,并尝试找出问题的触发因素。
2 启用APO后WordPress站点响应头常规内容解析
在正常情况下,使用 APO 时,响应头应类似如下:
Cache-Control: max-age=14400, must-revalidate
Cf-Apo-Via: tcache
Cf-Cache-Status: HIT
Cf-Edge-Cache: cache,platform=wordpress
但是我遇到的情况却是Cf-Cache-Status:BYPASS
,既缓存被绕过了,而原因是因为非常规的cookie,所以需要通过测试找出到底是什么cookie导致的这种情况。
在正常情况下,启用了 APO 后,访客浏览器访问 WordPress 站点时,收到的响应标头中应包含以下关键字段:Cache-Control、cf-apo-via、cf-cache-status、cf-edge-cache,这些字段的具体含义如下:
1、Cache-Control:定义缓存策略,指定内容的存储时长及是否需要重新验证,常见的值包括:
• max-age=xxxxx:表示缓存有效期为 xxxxx 秒。
• must-revalidate:要求在过期后重新验证内容的有效性,通常和max-age搭配使用。
• no-cache:表示缓存可以存储响应,但每次使用前都必须向源服务器重新验证(即进行 ETag 或 Last-Modified 头检查)。这样可以确保缓存的内容始终是最新的,但仍然可以减少完全下载的次数。
• no-store:表示绝对不允许存储响应数据,无论是浏览器缓存、CDN 还是代理服务器都不能保存该内容,每次请求都会重新获取完整的资源。这个值通常用于敏感数据(如登录页面、银行交易等)。
2、Cf-Apo-Via:表示 Cloudflare APO 的数据来源,常见的值包括:
• tcache:表示 APO 从 Cloudflare 边缘缓存中提供内容,属于正常缓存命中状态。
• origin,cookie:表示浏览器请求因携带非常规的 Cookie 而绕过了 APO 缓存,直接回源获取内容,这通常发生在用户登录、特定插件设置 Cookie,或某些自定义规则触发时。
• origin,no-cache:表示请求因 Cache-Control: no-cache 头的存在而绕过了 APO 缓存,直接回源获取内容。这意味着 Cloudflare 允许缓存该页面,但在提供缓存内容之前必须重新验证源站的最新状态(例如检查 ETag 或 Last-Modified)。如果源站响应未更改,Cloudflare 仍可能返回缓存内容,但会额外增加一次回源请求。
3、Cf-Cache-Status:指示 Cloudflare 缓存状态,可能的值包括:
• HIT:缓存命中,内容来自 Cloudflare 边缘节点的缓存。
• MISS:缓存未命中,需要回源获取。
• BYPASS:由于某些规则(例如 Cookie 或特定响应头)导致 Cloudflare 直接回源。
• EXPIRED:缓存已过期,需要重新获取并更新。
4、Cf-Edge-Cache:指示 APO 在 WordPress 站点上的缓存策略,通常为 “cache,platform=wordpress”,表示 Cloudflare 针对 WordPress 站点进行了缓存优化。
3 问题重现
3.1 模拟初次打开首页
我清除掉Chrome浏览器所有浏览记录和cookie信息之后,首次打开博客首页:

这时候访问是正常的,”CF-Cache-Status”的状态是”HIT”,而此时我的博客产生的cookie(第一方)内容如下(有6个cookie,主要是最下面3个):

这是APO缓存正常工作时候的cookie状态,先mark一下。
既然确认了首次访问首页没问题,那么进行下一步测试:重新加载(因为我遇到的情况是无论重新加载多少次都是BYPASS,所以不能不怀疑和重新加载有关)。
3.2 重新加载测试
3.2.1 重新加载的种类介绍
Chrome 浏览器提供的这3种不同的加载方式,每种方式的缓存处理逻辑不同:

- 正常重新加载(Normal Reload)
• 触发方式:按 F5 或点击刷新按钮
• 缓存行为:浏览器会向服务器发送请求,检查缓存是否仍然有效(通过 ETag、Last-Modified 等响应头);如果服务器返回 304 Not Modified,则继续使用缓存数据;仅对某些资源(如 HTML 文件)可能会重新拉取,其他资源(如 CSS、JS、图片等)可能仍然从缓存加载。
- 硬性重新加载(Hard Reload)
• 触发方式:按 Ctrl + Shift + R(Windows/Linux)或 Cmd + Shift + R(Mac)
• 缓存行为:强制从服务器重新下载所有资源(HTML、CSS、JS、图片等),但 Service Worker 可能仍然会拦截请求,从缓存中提供数据。
- 清空缓存并硬性重新加载(Empty Cache and Hard Reload)
• 触发方式:打开 开发者工具(F12 / Ctrl + Shift + I) → 右键刷新按钮 → 选择 “清空缓存并硬性重新加载”
• 缓存行为:清空所有缓存(包括 HTTP 缓存、存储的 Service Worker 缓存),强制从服务器重新下载所有资源。
总结如下:
加载方式 | 是否使用缓存 | 是否检查资源是否更新 | 是否清空缓存 |
---|---|---|---|
正常重新加载 | 可能使用缓存 | 是(通过 ETag、Last-Modified) | 否 |
硬性重新加载 | 不使用缓存 | 否,直接从服务器获取 | 否 |
清空缓存并硬性重新加载 | 不使用缓存 | 否,直接从服务器获取 | 是 |
实际应用场景
• 正常重新加载:一般情况下使用,减少不必要的资源下载,提高页面加载速度。
• 硬性重新加载:适用于发现页面更新不及时,或者某些资源(如 CSS、JS)似乎未正确更新的情况。
• 清空缓存并硬性重新加载:适用于排查缓存问题、调试 Web 应用,或确保最新资源被加载(例如前端开发或新部署后测试)。
第二种”硬性重新加载”和第三种”清空缓存并硬性重新加载”一般情况是差不多的,所以这次就只使用第一种和第二种重新加载方式。
注:如果是浏览器主动要求绕过缓存(例如硬刷新 Ctrl + F5 或者在开发者工具中禁用缓存),通常会在请求头中携带 “Cache-Control: no-cache”和”pragma: no-cache”,来表示要求服务器重新验证或直接获取最新内容。这种情况下,Cloudflare 也会遵循浏览器的请求,直接回源获取数据,而不会使用 APO 缓存。在 Cloudflare 的响应头中,这种回源行为可能会体现在 cf-apo-via: origin,no-cache,表示请求由于 no-cache 头的存在而绕过 APO 缓存。
3.2.2 正常重新加载和硬性重新加载测试
1、正常重新加载
先使用”正常重新加载”方式验证,我尝试了很多次首页的正常重新加载,以及选择了某一篇文章后进行的多次正常重新加载,都没有问题。
2、硬性重新加载
然后进行”硬性重新加载”方式验证,这时候”Cf-Cache-Status:BYPASS”第一次出现了:

不过,此时出现 “Cf-Cache-Status: BYPASS” 是正常现象,因为正如前文所述,硬性重新加载 本质上是浏览器主动请求跳过缓存,直接从源服务器获取最新内容。这一点可以从响应头中的 “Cache-Control: no-store, no-cache, must-revalidate” 以及 “Cf-Apo-Via: origin,no-cache” 体现出来,表明 Cloudflare 遵循了浏览器的请求,未使用 APO 缓存。
不过,接下来就不正常了,后续不管进行多少次的”正常重新加载”,”Cf-Cache-Status”永远都是”BYPASS”,并且”CF-Apo-Via”变成了”origin,cookie”:

也就是说,只要进行了一次”硬性重新加载”,就会出现某个(或者某些)非常规的cookie,这个(或者这些)非常规的cookie会一直存在,并会导致后续访问我博客上任何内容APO缓存都会失效,直接的表现就是”Cf-Cache-Status”都是”BYPASS”。
3.2.3 问题原因浅析
从上一节的分析中可以看出,响应标头的几个关键参数都发生了变化,明确表明 Cloudflare APO 未能命中缓存,而是直接回源请求页面内容。
首先,Cache-Control: no-store, no-cache, must-revalidate 说明源站明确指示浏览器和 Cloudflare 不要缓存页面,每次请求都必须重新验证甚至直接回源获取。其次,Cf-Apo-Via: origin,cookie 进一步表明 Cloudflare APO 因检测到影响缓存的 Cookie 而绕过了缓存,直接请求源站。最后,Cf-Cache-Status: BYPASS 证实了这一点,Cloudflare 认为该请求不符合缓存条件,因此未提供缓存。
综合来看,导致 APO 缓存被绕过的主要原因是特定的 Cookie,而这些 Cookie 可能是由于 硬性重新加载 触发的,也可能有其他未确认的触发因素。因此,真正需要排查的关键点在于 哪些操作会导致这些 Cookie 被设置,以及如何避免不必要的 Cookie 影响 APO 缓存。
那么这时候再来看看cookie的情况,多了4个出来,如下图红框中的蓝框所示:

这4个多出来的cookie中,”PHPSESSID”和”argon_user_token”是使用WordPress以及argon主题的网站都会有的,直接排除掉,而剩下的两个中,我一眼就锁定了”wp-editormd-lang”这个cookie,毕竟一看名称就不像正经cookie,是否就是由这个cookie引起的”Cf-Cache-Status: BYPASS”呢?
为了不冤枉好cookie,实际来验证一下,将”https://blog.tangwudi.com
“中的”wp-editormd-lang”这个cookie删除掉:


然后采用”正常重新加载”的方式验证一下,发现APO的缓存正常了:

这下就完全确定了,导致了”Cf-Cache-Status: BYPASS”问题的cookie就是”wp-editormd-lang”。
4.1 原因分析
看到这个 Cookie 名称,我当时就觉得眼熟,而且它还与Markdown 编辑器相关。我的 WordPress 站点上唯一安装的 Markdown 插件是 WP Editor.md,所以可以基本确定是它在设置 wp-editormd-lang 这个 Cookie。
为什么 “WP Editor.md” 要设置这个 Cookie?
从名称来看,wp-editormd-lang 可能用于存储 Markdown 编辑器的语言偏好,其作用可能包括:
- 记住用户的语言设置:例如,插件可能支持 zh-CN(中文)、en(英文)等不同语言,使用 Cookie 记录上次选择,以便下次访问时自动应用。
- 确保编辑器 UI 正确渲染:某些 UI 组件可能依赖这个 Cookie 来决定加载哪个语言文件。
- 可能涉及 AJAX 交互:如果插件的 Markdown 预览或解析功能需要通过 AJAX 加载,它可能会携带这个 Cookie,确保服务器返回正确的语言版本。
而为什么 Cloudflare APO 会 BYPASS 缓存?
Cloudflare APO 会检测 Cookie,若请求或响应中携带了 非 WordPress 核心的 Cookie,它可能会认为该页面是用户特定的动态内容,从而绕过缓存,直接回源获取页面。这是为了防止 缓存 A 用户的页面后,B 用户访问时看到错误的内容。
由于 wp-editormd-lang 不是 WordPress 核心的 Cookie,Cloudflare 可能默认认为该页面包含用户特定的内容,因此不缓存,导致 APO BYPASS(我猜的,不包正确率)。
4.2 解决问题
最直接解决这个问题的办法不外乎2种:
- 1、不要用WP Editor.md这个插件:不过我用得蛮好的,不想换其他的,太折腾,放弃
- 2、让WP Editor.md插件不要设置这个cookie:让作者改插件肯定不可能,不过可以想办法让WordPress本身来达成这个需求。
目前看来,第2种方式最可行,研究了一下,可以直接在functions.php中添加如下代码并保存:
add_action('init', function() {
if (!is_admin()) { // 只在前台页面执行
setcookie('wp-editormd-lang', '', time() - 3600, '/');
}
});
这段代码的作用是在”仅限前台页面”范围内清除 wp-editormd-lang Cookie,确保该 Cookie 不会影响 Cloudflare APO 缓存,从而避免 CF-Cache-Status: BYPASS 的问题。同时,由于不在 WordPress 后台执行,编辑器相关功能仍然可以正常使用,不受影响。这种方式既保留了 APO 的缓存优势,又不干扰后台的插件功能,是一种兼顾性能和功能的优化方案,不过唯一的劣势在于,”setcookie()” 只能在 HTTP 响应头发送前 执行,修改的是 浏览器在下一次请求时携带的 Cookie,而当前请求已经携带了”wp-editormd-lang”,Cloudflare APO 仍然会看到这个 Cookie,从而触发 BYPASS。
然后依次尝试:”硬性重新加载”,之后”wp-editormd-lang”cookie出现,然后再”正常重新加载”,”wp-editormd-lang”这个cookie就消失了,成功! 对这个结果感兴趣的朋友可以在自己的浏览器上访问我的博客来验证。
其实我现在不推荐直接修改任何WordPress的主题文件,因为经常改了之后过段时间就忘记了,久而久之主题文件被改得乱七八糟,连自己都不知道到底改了些什么,更别说如果要更换主题时如果要保留现在这些功能会多么折腾。
那么如果有功能需要修改WordPress的主题文件来实现一些功能,比如本文中这种功能,哪种方式最佳呢?其实可以通过WordPress的Code Snippets这个插件,免费版可以运行php和html代码,可以将原本需要添加到主题文件中来实现的这些功能,都通过插件进行管理,这对于管理来说非常方便:

注:而后我又有了另一个疑惑:”wp-editormd-lang”这个cookie的Expires时间是”会话(session)”,可为什么会一直存在?我中途不知道重启了多少次系统,浏览器也彻底关闭了不知道多少次,这个”会话”居然一直在?这不科学啊。除非是chrome浏览器的一个功能:”退出时保留会话”或”恢复上次会话”在这种场景下生效了,那么即使关闭了浏览器,Session Cookie 也可能会被恢复。于是我做了个尝试,先关闭”Code Snippets”插件中的php代码,然后关闭chrome浏览器的这个功能:在”设置”-“起始页面”,把原来的”继续浏览上次打开的网页”改为”打开新的标签页”:

最后清除所有cookie之后重新进行之前的测试,结果是”wp-editormd-lang”这个cookie在新开标签页或者浏览器彻底关闭后重新打开,都不在了,原来并不是 Cookie 本身有问题,而是”会话恢复”功能把它也带回来了,这算是浏览器的 “伪”会话保持机制把我坑了?不过这个发现其实也没啥用,因为大部分浏览器现在都是启用了该功能的。
5 后话
终于把这个蛋疼的问题解决了,不然 APO 订阅的 5 美金可就白花了!不过回过头来看,普通访问我博客的朋友一般不会没事去”硬性重新加载”页面,所以大概率真正受影响的只有我自己,搞了半天,结果像是一场 “一个人的狂欢”。
不过,换个角度来看,虽然这次修复的只是自己偶然触发的绕过缓存问题,但毕竟也通过层层排查,找到了”WP Editor.md 插件的 Cookie 影响 Cloudflare APO 缓存”这一根本原因,这不仅解决了当前APO缓存BYPASS的问题,更重要的还让我意识到:如果未来某些插件功能或 AJAX 请求也携带类似的 Cookie,可能同样会导致Cloudflare误判页面为动态内容,从而绕过APO的缓存,进而影响更多用户的访问速度。
因此,这次排查不仅优化了站点的稳定性和加载速度,还让我在分析和解决 APO 缓存异常方面积累了实战经验。另一方面,我对 Cache-Control 及相关响应标头参数的理解也更加透彻:从之前的一知半解变成了真正的掌握,所以,这次经历算是一次意外但收获颇丰的实战学习课程。
另:不得不又感叹一下,“写下来”真的太重要了。在没有打算把排查过程整理成文章之前,思路是零散的,东一下西一下,问题分析也流于表面,很多细节只是模糊的感觉,始终不得要领。而一旦开始写下来,就不得不对整个问题进行系统性梳理,不仅让思路更加清晰,还能发现之前忽略的细节,甚至在写的过程中意外找到突破点。更重要的是,这种写作不仅是记录,更是一种深度思考和知识沉淀。今天看似是解决了一个特定的问题,但这个过程本身也形成了一个可复用的排查思路,未来再遇到类似的情况,可以更快找到方向,甚至还能帮助别人少走弯路。
所以,写下来,不只是复盘,更是一种进步的方式。
博主的Wordpress是建在家里的服务器上了嘛
对,群站所有的应用都部署在家里,只有图床部署在R2 上,方便多个站点切换。