上一篇继续排查了 W3 Total Cache 页面缓存长期很难看到超过 2 天的问题。
最终先发现了一条已经持续了一段时间的故障链:
Yoast post-sitemap.xml
↓
PHP-FPM 256M 内存耗尽
↓
HTTP 500
↓
联合预热 Sitemap 长期生成失败
↓
W3TC 无法持续获得最新的预热 URL将 PHP-FPM 的 memory_limit 从实际生效的 256M 提高到 512M 后,中英文 post-sitemap.xml 都恢复了 HTTP 200,联合 Sitemap 也重新开始自动更新。
到这里,我原本以为 W3TC 页面缓存预热的问题基本解决了。
但继续检查以后,又发现了一个更加奇怪的问题:
W3 Total Cache 的 Prime 和 Cleanup 定时任务虽然配置存在,却没有正常执行。手工重新创建以后,任务甚至还会再次消失。
继续往下排查,最终复现出了一组非常关键的结果:
数据库中的 cron option:YES
WordPress get_option("cron"):NO也就是说:
MySQL 中保存的 Cron 数据已经是新版本,但普通 PHP 环境从 Object Cache 读取到的,却仍然是一份旧数据。
最后通过精准删除 Redis Object Cache 中的 options/alloptions,Cron 数据重新恢复一致。
这一篇就记录整个排查过程。
一、Sitemap 已经恢复,但 W3TC Prime 还是没有正常工作
W3 Total Cache 当前开启了 Page Cache Preload。
配置大致为:
Page Cache:Disk Enhanced
Prime:
每 300 秒执行一次
每次预热 7 个 URL
Cleanup:
每 3600 秒执行一次对应的两个 WP-Cron Event 是:
w3_pgcache_prime
w3_pgcache_cleanup联合 Sitemap 恢复以后,我开始确认这两个任务到底有没有正常运行。
结果发现它们的状态明显不对。
重新创建以后,WP-CLI 能够看到:
w3_pgcache_prime
2026-09-01 04:09:28
now
5 minutes
w3_pgcache_cleanup
2026-09-01 04:09:28
now
1 hour
这里显示的是 GMT 时间。
对应北京时间就是:
12:09:28Prime 的周期是 5 分钟,Cleanup 的周期是 1 小时。
正常情况下,只要 WP-Cron 真正执行一次,这两个任务下一次执行时间就应该分别向后推进。
但实际情况并不是这样。
二、Linux Cron 明明一直在执行 wp-cron.php
我的 WordPress 已经关闭了由前台访问自动触发 WP-Cron:
DISABLE_WP_CRON = true服务器则使用 Linux Cron,每 5 分钟直接执行一次:
*/5 * * * * cd /data/wwwroot/www.shuijingwanwq.com && /usr/local/php/bin/php wp-cron.php >/dev/null 2>&1因此理论上的链路是:
Linux crond
↓
php wp-cron.php
↓
WordPress WP-Cron
↓
w3_pgcache_prime
w3_pgcache_cleanup检查 /var/log/cron 后,也能够确认 wp-cron.php 每 5 分钟确实都在执行。
所以问题并不是:
Linux Cron 没启动。
甚至手工执行:
/usr/local/php/bin/php wp-cron.php也能够正常返回:
exit_code=0但当时两个 W3TC Event 的时间仍然没有按照预期推进。
于是问题只能继续往 WordPress Cron 内部追。
三、先逐项排除最常见的 WP-Cron 问题
接下来检查了几个最直接的可能性。
首先确认:
wp_get_ready_cron_jobs()能够看到:
w3_pgcache_prime
w3_pgcache_cleanup也就是说 WordPress 明确认定它们已经到执行时间。
再检查:
has_action("w3_pgcache_prime")
has_action("w3_pgcache_cleanup")两者也都已经注册 callback。
同时:
get_transient("doing_cron")没有发现长期残留的 Cron lock。
因此逐步排除了:
任务根本不存在 ×
任务还没有到时间 ×
W3TC callback 没有注册 ×
长期 doing_cron 锁卡死 ×
Linux crond 没运行 ×到这里问题已经有些反常了。
四、更奇怪的是,两个 W3TC Event 后来直接从数据库消失了
继续检查以后,出现了一个比“不执行”更加奇怪的现象。
直接读取 WordPress 数据库里的:
wp_options
option_name = cron发现:
w3_pgcache_prime:NOT IN DATABASE
w3_pgcache_cleanup:NOT IN DATABASE也就是说:
刚刚才创建成功的两个 W3TC Cron Event,后来竟然从真正的数据库中消失了。
如果只是 W3TC Prime callback 执行失败,理论上很难解释这种现象。
这时我开始怀疑:
会不会不是某个 Event 被单独删除,而是整个
cronoption 曾经被一份旧数据覆盖?
为了验证这个猜测,需要一个完全与 W3TC 无关的实验任务。
五、创建一个 WordPress 插件根本不认识的 Probe
我先创建了一个临时 Cron Event:
swq_cron_persist_probe并把执行时间安排在大约 1 小时以后。
这个名字是临时生成的,任何现有插件都不可能提前针对它编写删除逻辑。
创建以后检查:
schedule_result=bool(true)
DB_contains_probe=YES
get_option_contains_probe=YES说明任务确实已经写入数据库。
然后立即执行:
/usr/local/php/bin/php wp-cron.php注意:
这个 Probe 还有大约 1 小时才到期,所以此时的
wp-cron.php根本不应该处理它。
但执行以后再次检查:
DB_contains_probe=NO
get_option_contains_probe=NO
next_scheduled=bool(false)Probe 居然消失了。
这一下就基本排除了:
W3TC 自己专门清除了
w3_pgcache_prime。
因为一个刚刚才临时起名字、还没有到期的 Event 也一样会消失。
真正值得怀疑的变成:
wp-cron.php 启动
↓
读取到旧的 Cron 数组
↓
执行某个到期 Event
↓
重新写回 cron option
↓
后来新增的 Event 被旧数组一起覆盖不过还需要进一步证明:
WordPress 到底有没有真的读到一份旧 Cron 数据?
六、关键证据出现:DB=YES,但 get_option=NO
于是又建立了第二个 Probe:
swq_cron_persist_probe_2这次安排在大约 2 小时以后。
创建以后直接查询数据库:
schedule=bool(true)
DB=YES说明数据库中确实已经存在这个 Event。
然后启动一个全新的普通 PHP 进程,通过 WordPress:
get_option("cron")读取。
结果变成:
get_option=NO
DB=YES
这组结果非常关键。
因为同一个时间点:
MySQL:
Probe = YES
WordPress get_option("cron"):
Probe = NO已经不能再用“任务没创建成功”来解释。
数据库明明是新的。
但 WordPress API 读出来的却是旧的。
进一步在:
define("DOING_CRON", true);的环境中检查,结果仍然一样:
get_option=NO
DB=YES因此问题也不是简单的:
只有 WP-Cron 环境才看不到任务。
更准确地说,是新的普通 PHP 进程取得的 option 数据就已经和数据库不一致了。
七、把注意力转向 W3TC Redis Object Cache
当前 WordPress 开启了 W3 Total Cache Object Cache:
Object Cache:Enabled
Engine:Redis而 WordPress 的:
cron又是一个 autoload option。
检查数据库后得到:
autoload=on这意味着它通常会随着大量自动加载配置一起进入:
alloptions因此:
get_option("cron")不一定每一次都直接查询 MySQL。
在开启持久化 Object Cache 的环境中,它很可能直接读取已经缓存的:
group:options
key:alloptions到这里,前面的现象就有了一个非常合理的解释:
数据库中的 cron
已经更新
↓
Redis 中的 alloptions
仍然是旧版本
↓
新的 PHP 进程
get_option("cron")
↓
得到旧数据但猜测还不够,下一步直接验证。
八、精准删除 alloptions,而不是清空全部缓存
这里我没有执行:
W3TC Flush All也没有清空 Page Cache。
因为整个排查本来就是为了弄清楚页面缓存生命周期异常的问题,如果此时再把全部页面缓存清掉,反而会破坏后续观察。
所以只针对:
options / alloptions做最小范围处理。
一开始通过 WP-CLI 删除以后,曾出现一个很有意思的情况:
alloptions=bool(true)但新的普通 PHP 进程仍然:
get_option=NO
DB=YES这说明当前环境中:
WP-CLI 与普通 PHP 对 Object Cache 的实际观察结果存在差异。
因此后来改成与 wp-cron.php 相同的普通 PHP 环境执行:
wp_cache_delete("alloptions", "options");结果:
delete_alloptions=bool(true)紧接着启动一个新的普通 PHP 进程重新读取。
这一次变成:
get_option=YES
DB=YES
这一组前后对比已经非常直接:
删除前:
get_option=NO
DB=YES
↓
删除 alloptions
↓
删除后:
get_option=YES
DB=YES因此,到这里已经可以比较明确地确认:
这次 WP-Cron Event 消失的直接故障机制,与陈旧的
alloptionsObject Cache 有关。
九、为什么陈旧 alloptions 会导致未来 Event 一起消失?
WordPress Cron 的所有任务,本质上都保存在:
option_name = cron这一份结构化数据中。
如果数据库里已经是:
Cron Version B其中包含:
旧任务
+
新加入的 W3TC Prime
+
新加入的 Cleanup
+
Probe但某个 PHP 进程从 Redis Object Cache 中取得的仍然是:
Cron Version A只包含:
旧任务那么当这个进程执行某个已到期任务并触发:
wp_reschedule_event()或者:
wp_unschedule_event()时,就有可能基于自己手里这份旧数组重新写回数据库。
于是:
数据库 Version B
↓
被 Version A 重新覆盖
↓
后来新增的 Event 全部消失这也解释了之前那个最反常的现象:
为什么一个还有 1 小时、2 小时才执行的 Probe,也会在运行
wp-cron.php后提前消失。
它并不是被 Cron “执行”了。
而更可能是:
它所在的新版本 Cron 数据整体被旧版本覆盖掉了。
十、重新建立 Prime、Cleanup 和 Probe
清理陈旧的 alloptions 以后,我重新创建:
w3_pgcache_prime
w3_pgcache_cleanup并保留:
swq_cron_persist_probe_2用于继续验证。
随后数据库检查得到:
DB w3_pgcache_prime=YES
DB w3_pgcache_cleanup=YES
DB swq_cron_persist_probe_2=YES
截图因为终端一屏显示空间有限,没有完整包含前面的创建命令。
不过这里真正需要确认的是最终数据库状态:
Prime = YES
Cleanup = YES
Probe = YES说明三个 Event 此时已经同时存在。
同时检查它们的执行时间:
w3_pgcache_prime
05:39:35 GMT
w3_pgcache_cleanup
05:39:35 GMT
swq_cron_persist_probe_2
07:34:18 GMT其中 Prime 和 Cleanup 已经到期,Probe 仍然要将近两个小时以后才执行。
正好适合再次测试 wp-cron.php。
十一、再次执行 wp-cron.php,这一次终于正常了
执行:
/usr/local/php/bin/php wp-cron.php以后再次检查。
Prime 从:
05:39:35 GMT变成:
05:44:35 GMT刚好:
+5 分钟Cleanup 从:
05:39:35 GMT变成:
06:39:35 GMT刚好:
+1 小时而 Probe:
07:34:18 GMT保持完全不变。
数据库中:
DB w3_pgcache_prime=YES
DB w3_pgcache_cleanup=YES
DB swq_cron_persist_probe_2=YES也全部继续存在。

这一次的结果与故障状态形成了非常鲜明的对比。
修复前:
执行 wp-cron.php
↓
未来 Probe 消失修复后:
执行 wp-cron.php
↓
Prime +5 分钟
Cleanup +1 小时
Probe 不变到这里,手工测试已经形成闭环。
十二、最后还需要证明系统 Cron 自己也能正常工作
手工运行正常还不够。
生产环境实际依赖的是 Linux crond:
*/5 * * * *所以最后我没有再手工执行 wp-cron.php,而是等待下一次系统 Cron。
13:45:01,/var/log/cron 中明确出现:
Sep 1 13:45:01 ...
/usr/local/php/bin/php wp-cron.php
这张截图因为终端显示空间有限,只保留了系统 Cron 的实际执行记录。
随后继续检查 W3TC Event 状态,Prime 已经从此前的:
05:44:35 GMT自动推进到:
05:49:35 GMT而尚未到期的 Cleanup 仍然保持:
06:39:35 GMT这意味着真正的生产链路也已经恢复:
Linux crond
↓
php wp-cron.php
↓
WordPress Cron
↓
w3_pgcache_prime
↓
下一次执行时间 +5 分钟所以最终并不是:
只有手工运行才正常。
系统每 5 分钟一次的真实 Cron 也已经能够正常驱动 W3TC Prime。
十三、这次真正可以确定的是什么?
经过这一轮实验,有几个结论已经比较明确。
1. Linux crond 没有坏
日志一直显示:
每 5 分钟执行一次 wp-cron.php最后修复完成后,同一套 Cron 配置也确实能够让 Prime 自动推进。
所以没有必要修改 Linux Cron。
2. wp-cron.php 本身也没有坏
故障期间:
php wp-cron.php虽然返回:
exit_code=0但行为异常。
清理陈旧 alloptions 以后,完全相同的:
php wp-cron.php马上恢复正常。
所以问题并不在 wp-cron.php 文件本身。
3. W3TC Prime / Cleanup callback 本身没有坏
恢复一致性以后:
Prime +5 分钟
Cleanup +1 小时都完全符合 W3TC 当前配置。
因此也没有必要修改 Prime callback。
4. 直接故障机制已经定位到陈旧 alloptions
最关键的实测结果仍然是:
DB=YES
get_option=NO随后:
delete alloptions立即变成:
DB=YES
get_option=YES这是整个排查过程中最重要的一组证据。
十四、但最底层根因还没有完全结束
这里需要区分两个概念:
找到直接故障机制,不等于已经找到了最底层根因。
现在已经知道:
普通 PHP 环境中的 alloptions
曾经存在陈旧数据但还没有完全解释:
为什么数据库中的 autoload option 已经更新,而这份 Redis Object Cache 却没有同步失效?
我的 WordPress 环境又比较特殊。
目前至少涉及:
www.shuijingwanwq.com
en.shuijingwanwq.com
admin.shuijingwanwq.com后台主要通过:
admin.shuijingwanwq.com访问,同时使用 Polylang 管理中英文内容。
此前为了处理 Polylang 与 W3 Total Cache 在多域名环境中的缓存问题,我已经增加过 MU Plugin。
但从这次情况来看:
现有兼容处理可能并没有覆盖完整的多域名缓存一致性问题。
不过目前还不能直接断言:
admin 域名
↓
导致 alloptions 陈旧因为还需要继续确认:
- WP-CLI 与普通 PHP 使用的 Redis namespace 是否完全一样;
admin、www、en三个 Host 下的 Object Cache key 是否真的一致;- W3TC 是否对 WP-CLI 使用了特殊 Object Cache 处理;
- 当前 MU Plugin 到底只处理了哪些 Page Cache / Object Cache 场景。
这一部分更适合以后单独调查。
十五、Page Cache 和 Object Cache 也不能混为一谈
这次排查还让我更加明确了一点:
W3TC Page Cache和:
W3TC Object Cache虽然都叫“缓存”,但其实是两套完全不同的问题。
Page Cache 当前使用:
Disk Enhanced主要缓存的是最终 HTML 页面。
而这一次影响 WP-Cron 的:
alloptions属于:
Object Cache / Redis所以不能因为 Object Cache 出现了数据一致性问题,就直接认为:
W3TC 的页面文件缓存也一定坏了。
反过来也一样。
接下来观察:
4 天 Page Cache TTL是否终于能够逐渐出现 2 天、3 天甚至接近 4 天的缓存文件,仍然需要单独进行。
十六、为什么这次没有直接 Flush All?
整个过程中我一直尽量避免:
W3TC Flush All原因也很简单。
这台服务器只有 2 vCPU,而且这一次问题本来就是因为 CPU 告警才重新开始排查。
如果直接清空全部页面缓存,随后大量页面都需要重新生成,反而可能制造一次新的缓存重建压力。
而且这一次已经能够把问题精确缩小到:
options / alloptions那么最合理的做法就是:
只处理真正异常的缓存而不是:
为了省事把所有缓存全部清掉最终也确实证明,只处理 alloptions 就足以恢复 Cron 数据一致性。
十七、以后服务器终端中的 WordPress 操作尽量统一使用 WP-CLI
这次为了定位问题,我交替使用了:
WP-CLI
普通 PHP CLI
wp-cron.php
数据库直接查询这是故障诊断所必须的。
特别是如果没有刻意比较:
WP-CLI
vs
普通 PHP可能根本发现不了:
DB=YES
get_option=NO这样的差异。
不过日常运维长期这样混用,会增加很多理解成本。
所以后续准备尽量遵循一个原则:
服务器终端中的 WordPress 日常管理
→ 优先 WP-CLI
需要验证 Web / PHP / WP-Cron 真实运行环境
→ 才临时使用普通 PHP至于目前系统 Cron:
/usr/local/php/bin/php wp-cron.php暂时不准备为了“形式统一”改成:
wp cron event run --due-now因为现有生产链路已经经过真实验证:
Linux crond
→ php wp-cron.php
→ W3TC Prime现在运行正常。
在 WP-CLI 与普通 PHP 的 Object Cache 差异还没有完全调查清楚以前,没有必要再额外改变一个已经正常工作的生产入口。
十八、三层问题终于逐步拆开
回头看这次 CPU 告警,最后实际上拆出了三层完全不同的问题。
第一层是流量入口:
短时间大量异常访问
↓
EdgeOne
↓
自适应频控
JavaScript Challenge
流量防盗刷第二层是页面预热入口:
Yoast post-sitemap.xml
↓
256M OOM
↓
HTTP 500
↓
联合 Sitemap 长期无法更新第三层则是 WordPress Cron 与 Object Cache:
cron option 已经更新
↓
Redis alloptions 仍然陈旧
↓
get_option("cron") 读到旧值
↓
wp-cron.php 用旧 Cron 数据工作
↓
后来创建的 Event 被覆盖三个问题叠在一起时,很容易感觉:
W3TC 好像哪里都不正常。
但真正逐层拆开以后,每一层实际上都能够独立验证。
这也是这次排查最有价值的地方。
十九、接下来先观察,不再继续大改配置
目前 W3TC Prime 已经恢复:
每 5 分钟
预热 7 个 URLCleanup:
每 1 小时联合 Sitemap 也已经恢复持续更新。
Page Cache 生命周期仍然保持:
4 天现在更重要的事情不是继续调整参数,而是让系统稳定运行一段时间。
如果后续缓存目录逐渐开始出现:
1 天
2 天
3 天的页面缓存,就说明最开始那个:
为什么 4 天 TTL 却长期看不到超过 2 天的页面缓存?
也终于开始得到实际验证。
而多域名、Polylang、admin.shuijingwanwq.com、WP-CLI 与 W3TC Redis Object Cache 之间为什么会产生这次 alloptions 不一致,则可以作为下一轮独立排查的问题。
至少目前,这次从 CPU 告警一路挖出来的三个问题,终于都已经有了比较明确的处理结果。
WordPress 网站维护、性能优化与博客运营咨询
本站已持续运营超过 10 年,累计发布 1000+ 篇原创技术文章,长期实践 WordPress 网站建设、CDN / Cloudflare 配置、缓存优化、Google SEO、广告变现和多语言网站运营。
如果你的 WordPress 网站遇到访问慢、缓存异常、插件冲突、广告不显示、SEO 基础结构混乱、CDN 配置不确定等问题,可以联系我做一次远程技术排查。
适合以下用户:
✅ 个人博客站长
✅ WordPress 网站运营者
✅ 独立开发者与内容创作者
✅ SaaS 产品官网运营团队
✅ 希望优化网站速度与稳定性的站点
服务内容:
✅ WordPress 速度优化
✅ Cloudflare / CDN / 缓存配置排查
✅ 插件冲突与页面异常排查
✅ AdSense 广告显示问题排查
✅ SEO 基础结构检查
✅ 博客运营与商业化咨询
如需了解方案或交流相关问题,请直接联系我,并注明:WordPress 维护咨询。
联系方式:
Telegram:@shuijingwan
微信:13980074657
邮箱:shuijingwanwq@gmail.com
