WordPress 定时发布不生效:WP-Cron 触发机制、时区设置与主机侧限制排查

WordPress 定时发布不生效的排查路径封面

文章在后台排好了时间,到点却没上线,状态还停在「已排期」,或者多出一行 Missed schedule。这类 WordPress 定时发布故障不报错,日志里也没有红字,很多人先去怀疑主机,换套餐、重装插件,问题照旧。本文按「先确认哪一环没跑」的顺序,教你用 WP-CLI、几条 SQL 和一次 curl 请求把原因缩到一个具体环节,再决定改时区、修 loopback,还是交给系统计划任务。

先确认卡在哪一层:状态、事件、时间戳

排期发布依赖三份数据:文章状态(future 表示已排期)、wp_options 里的 cron 事件(钩子名 publish_future_post)、事件时间戳。三者分开保存,同样的现象可能来自完全不同的原因。后台出现 Missed schedule,说明计划时间已过而状态仍是 future,发布事件没执行或已丢失。

wp post list --post_status=future --fields=ID,post_title,post_date,post_date_gmt
wp cron event list --hook=publish_future_post
wp option get doing_cron

第一条列出仍在排期的文章,把 ID 对照第二条输出里有没有对应事件。事件缺失时改时区、换主机都无效,因为没有东西等着触发;执行 wp post update <ID> --post_status=future 重新排期,WordPress 会按当前时区重建事件。第三条看并发锁,残留未来时间戳会让所有请求跳过调度。

请求触发 wp-cron.php 的执行路径

WP-Cron 的触发机制:没有常驻进程,靠访问推动

wp-cron.php 是被页面请求顺路叫起来的:PHP 请求结束前,WordPress 检查有没有到期任务,有的话就向站点自己发起一次非阻塞 HTTP 请求(loopback,服务器请求自身域名的回环访问),由子请求执行任务。

由此带来三条前提:

  • 站点要有持续访问,或由系统计划任务定期请求 wp-cron.php;缓存命中率极高、真实 PHP 请求趋近于零时,任务只能排队。
  • loopback 必须通。主机防火墙、WAF(Web 应用防火墙,用于拦截异常 HTTP 请求)规则、整站 HTTP 认证都会让请求失败,而前台不报错。
  • 并发靠锁串行化。doing_cron 记录带时限的锁(WP_CRON_LOCK_TIMEOUT,默认 60 秒),锁未释放时后续请求跳过调度。

验证只需两步:wp option get doing_cron 看锁,curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/wp-cron.php 看返回码。返回 403、503 或超时而首页正常,基本可判定 loopback 或 WAF 在拦。

时区设置:差 8 小时最常见

WordPress 存两个时间:post_date 是站点本地时间,post_date_gmt 是 UTC,注册事件时用它俩换算时间戳。后台时区若停在 UTC+0,而你按北京时间填 20:00,事件会在次日 04:00 才触发,现象就是晚了 8 小时。

改时区不会修正已排期文章:时间戳创建时就固定了。顺序是先改时区,再让原文重新进入 future,最后用 wp post get <ID> --field=post_date_gmt 核对。

系统时钟漂移更隐蔽:PHP 取操作系统时间,系统慢 7 分钟任务就整体晚 7 分钟。用 date -u 与实际 UTC 对比,差距超 1 分钟就该处理,校准见服务器时间同步配置指南。

时区偏移导致排期与实际发布相差 8 小时

主机侧的三类限制

共享型虚拟主机(多个站点共用同一台服务器 CPU、内存与进程配额)上的失败,多数落在三类限制里。

一是 wp-config.php 残留 define('DISABLE_WP_CRON', true);。这行早年常被当作降负载优化写入,迁移脚本会顺手带上。禁用后必须由系统计划任务访问 wp-cron.php,否则永远不跑。用 grep -n "DISABLE_WP_CRON" wp-config.php 确认,删配置或补系统任务,二者必选其一。

二是资源配额把 cron 进程杀掉。计划任务常用来跑备份、生成缩略图、清理修订版本,触及 CPU 或内存上限即被终止,后台只留「跑过但没结果」的印象。WordPress 网站备份策略里的分片错峰思路就是为此;同时留意 PHP 日志的 Maximum execution time。

三是系统计划任务没执行。控制面板里的命令路径、PHP 版本、执行用户任一项不对都会静默失败。顺序是:先确认 cron 服务在跑,再确认命令能手动执行成功,最后才怀疑调度表达式,详见crontab 不执行排查。在共享主机与独立资源间摇摆的,可参考共享主机与独立服务器的取舍,自主配置 cron 的能力正是关键差别。

WP-Cron 触发链路上的三类主机侧限制

插件与缓存带来的误判

缓存插件普遍把预加载、清理缓存、生成站点地图挂在 WP-Cron 上,所以 WP-Cron 一卡,同时出问题的还有「缓存不更新」「缩略图不生成」这些看似无关的现象。先停用缓存插件的定时任务再测发布:停用后恢复,问题在任务执行侧;依旧不发,问题在排期事件本身。

对象缓存也会制造假象:doing_cron 锁写进 Redis 或 Memcached 且过期策略不当时会长期有效,表现为排期不执行、手动发布正常。清一次对象缓存再重试,是最低成本的验证。

插件冲突同样会让保存排期失败而不报错,例如两个插件都接管 publish_future_post。定位方法与WordPress 插件冲突导致白屏一致:先全部停用确认恢复,再按「最近安装的优先」逐个启用。

一条可复现的定位顺序

按下面五步走,多数情况十几分钟就能收敛到结论。

  1. 运行第 1 节的第一条命令,确认状态仍是 future,记下两个时间字段。
  2. 运行第二条命令确认事件存在并记下 UTC 时间;缺失就重新排期。
  3. 拿事件 UTC 时间加站点时区反推本地时间,与后台填的对比;对不上即时区或时钟问题。
  4. 用 curl 探测 wp-cron.php 返回码,再执行 wp option get doing_cron;403、503 或残留锁都属触发端阻塞。
  5. 执行 wp cron event run --due-now:能发出去说明触发端有问题,发不出去说明事件参数或权限有问题。

第 5 步会把到期任务真跑一遍,生产站先确认没有进行中的备份或同步,避免两个进程争抢同一批文件。

修好之后怎么验证

改完配置不等于解决,要用一次真实触发证明。新建一篇测试文章排在 10 分钟后,正文写两句话即可。到点确认三件事:状态是否变成 publish、前台能否打开、wp cron event list 里对应事件是否已清除。

三次测试都准点说明链路已通;仍偶发失败就比对主机资源告警时间与任务执行时间是否重合。测试文章用完删掉。

什么时候该把 WP-Cron 交给系统 cron

判断标准不是站点大小,而是访问是否稳定密集。缓存命中率高的站点真实 PHP 请求很少,loopback 被缓存层和低频访问一起削弱;日访问量个位数的企业站同理,没人在线就没人触发。这类站点更适合用系统 cron 每 5 分钟执行 wp cron event run --due-now,配置见WP Cron 替代为系统 Cron 任务实战。

如果你用的是 Hostease 虚拟主机,控制面板里提供计划任务入口,命令路径与 PHP 版本以面板实际显示为准,拿不准时先与客服确认。需要自行接管系统服务时,VPS(虚拟专用服务器)或独立服务器才给完整的 crontab 与日志权限。

总结一下:定时发布不生效不是单一故障,而是「状态、事件、时间戳、触发、资源」五个环节里某处断了。先用 WP-CLI 确认事件在不在,再核对时区与系统时钟,最后才查主机限制与插件干扰。建议每次改完配置都排一篇 10 分钟后的测试稿验证;如果你需要长期跑大量计划任务,可以考虑把触发方式固定为系统 cron,并单独配日志与告警。

发表评论