首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >WP-Cron 和系统 cron 到底差在哪:什么时候该换、怎么配、怎么确认任务真的跑了

WP-Cron 和系统 cron 到底差在哪:什么时候该换、怎么配、怎么确认任务真的跑了

原创
作者头像
用户10345225
发布于 2026-11-02 12:31:48
发布于 2026-11-02 12:31:48
450
举报

一个很常见的现场:备份插件写着「上次运行:3 天前」,可站点访问正常、后台也没报错。再一看,站点是企业站,一天没几个访客,还挂着整页缓存。插件本身没坏:WordPress 的定时任务压根没被触发。

WP-Cron 不是一个常驻的调度器

WordPress 把「什么时候该做什么」记在数据库里(wp_options 的 cron 选项),本身没有守护进程。它靠的是:每次有 PHP 请求进来,在初始化之后顺手看一眼有没有到点的事件,有就发一个 HTTP 请求去「请求自己」的 wp-cron.php,由那个请求把到点的钩子跑掉(wp_cron() → spawn_cron())。

这套机制带来几个直接后果:

  • 没人访问就不会跑。凌晨三点的备份不会在三点执行,会等到第二天第一个访客。
  • 页面被整页缓存或被 CDN 挡住、PHP 没执行,就不会触发。
  • 站点在维护模式、或者整个站挂在 Basic Auth 后面,环回请求可能被拒,事件就一直堆着。
  • 并发请求多时,靠 doing_cron 这个 transient 做锁,WP_CRON_LOCK_TIMEOUT(默认 60 秒)内不重复 spawn。它是个锁,不是队列——锁过期后同一条长任务可能被第二次启动。

判断当前站点处在哪种状态,先跑这两条:

代码语言:bash
复制
wp cron test
wp cron event list

wp cron test 会告诉你 WP-Cron 是不是被禁用了、环回请求通不通。wp cron event list 的默认字段是 hook、next_run_relative、recurrence,盯着 next_run_relative 看:它一直停在 now 附近不动,就说明没人推动它。

什么情况下该换成系统 cron

该不该换,跟站点大小关系不大,看的是错过一次会不会有人来问你。

需要换:

  • 有对时间敏感的事:定时发布、会员到期、订阅续费、对外接口同步、按小时清理临时数据。
  • 站点流量低,或者页面几乎全被缓存。
  • 需要知道失败。请求触发的模式下,任务挂了只会静默。
  • 任务本身长(批量导入、批量发信),不想被访客请求触发,也不想受 PHP-FPM 的请求超时限制。

可以先不换:个人博客、一天跑一次晚点也无所谓、主机面板已经统一接管了调度。

换过去的三步

关掉请求触发的 spawn

代码语言:php
复制
// wp-config.php,放在 /* That's all, stop editing! */ 之前
define('DISABLE_WP_CRON', true);

只关这个常量是不够的:wp-cron.php 仍然是一个可以被外部直接访问的 URL,别人可以反复打它来触发你的任务。在 web 层把它挡掉,只留本机:

代码语言:nginx
复制
location = /wp-cron.php {
    allow 127.0.0.1;
    deny all;
}

系统 cron 走 WP-CLI,不经过 web,不受影响。

用站点文件所有者的身份跑

写一个包装脚本,把时间戳和退出码一起记进日志——只重定向命令输出的话,没有事件可跑的那几分钟日志里什么都不会有,后面做「日志多久没更新」的监控就没法判断。

代码语言:bash
复制
#!/usr/bin/env bash
# /usr/local/bin/wp-cron-example.sh
set -uo pipefail
cd /var/www/example.com/current || exit 1
printf '[%s] start\n' "$(date -u +%FT%TZ)"
rc=0
/usr/local/bin/wp cron event run --due-now || rc=$?
printf '[%s] exit=%d\n' "$(date -u +%FT%TZ)" "$rc"
exit "$rc"
代码语言:bash
复制
chmod +x /usr/local/bin/wp-cron-example.sh
crontab -u www-data -e
代码语言:cron
复制
*/5 * * * * /usr/local/bin/wp-cron-example.sh >> /var/log/wp-cron.log 2>&1

几个容易踩的点:

  • 用 root 跑会写出一堆 root 属主的上传文件、缓存和日志,后面很难收拾。直接把任务放进 www-data 的 crontab,比 sudo -u 干净。
  • crontab 里的 % 有特殊含义(会被当成换行),所以别把 date +%F 直接写进 crontab,塞进脚本里就没这个问题。
  • 5 分钟一次意味着事件最多晚 5 分钟。调更短只是多起 PHP 进程,误差不会更小。
  • 站点本身有 fatal error 或连不上数据库时,这条命令也会失败。日志里有,页面上看不出来(页面是缓存)。

长任务用 systemd timer 更好排错

代码语言:ini
复制
# /etc/systemd/system/wp-cron-example.service
[Unit]
Description=WP-Cron for example.com

[Service]
Type=oneshot
User=www-data
WorkingDirectory=/var/www/example.com/current
ExecStart=/usr/local/bin/wp cron event run --due-now
TimeoutStartSec=300
代码语言:ini
复制
# /etc/systemd/system/wp-cron-example.timer
[Unit]
Description=Run WP-Cron for example.com every 5 minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=30s

[Install]
WantedBy=timers.target
代码语言:bash
复制
systemctl daemon-reload
systemctl enable --now wp-cron-example.timer
systemctl list-timers | grep wp-cron
journalctl -u wp-cron-example.service -n 50 --no-pager

同一个 service 在上一次还没结束时不会再起一个,重叠问题天然被挡住;输出也直接进 journal,不用自己管日志轮转。用 crontab 的话,加一把 flock -n 也能防重叠。

这套配置也可以直接买人做

上面三步自己走一遍大概半小时,站点一多,每个都要再改一遍路径、再配一次日志轮转和告警,容易漏。AumCreate 做 WordPress 代部署,是把模板装到客户自己的主机上、每一页换成客户的内容,这种「关掉请求触发的 spawn、改用系统调度、脚本用站点文件所有者跑」的活儿放在交付前一次做完,主机、域名、源码都在客户手里,之后要自己改也不会被谁卡着。代部署 $399,材料收齐后 5 个工作日内上线,细节和需要准备的材料在 setup 页。愿意自己动手的话,上面几段脚本改改路径就能用。

怎么确认任务真的跑了

分三层看,只看其中一层容易误判。

调度层:wp cron event list --fields=hook,next_run_relative,recurrence,next_run_relative 应该持续推进。

执行层:tail -f /var/log/wp-cron.log,每 5 分钟应该有成对的 start / exit 行,exit 不为 0 就要查。

业务层:很多人到这一步就停了,但调度器跑了不等于你的回调跑了。放一个探针 mu-plugin:

代码语言:php
复制
<?php
// wp-content/mu-plugins/cron-probe.php
add_action('cron_probe_event', function () {
    $line = sprintf("[%s] probe ok pid=%d\n", gmdate('Y-m-d\TH:i:s\Z'), getmypid());
    file_put_contents(WP_CONTENT_DIR . '/cron-probe.log', $line, FILE_APPEND | LOCK_EX);
});
代码语言:bash
复制
wp cron event schedule cron_probe_event now hourly
wp cron event list --hook=cron_probe_event
# 等一个周期后
tail -n 5 wp-content/cron-probe.log

探针立刻验证一次也行:wp cron event run cron_probe_event。验完记得 wp cron event delete cron_probe_event 并删掉 mu-plugin。

日志不更新就报警,用一个最小的检查脚本挂在另一台机器上(用 stat -c %Y 取修改时间,GNU coreutils 才有,BSD 上换成 stat -f %m):

代码语言:bash
复制
#!/usr/bin/env bash
set -u
LOG=/var/log/wp-cron.log
MAX_AGE=900
age=$(( $(date +%s) - $(stat -c %Y "$LOG") ))
if [ "$age" -gt "$MAX_AGE" ]; then
  echo "wp-cron log stale on $(hostname): ${age}s" | mail -s "wp-cron stale" ops@example.com
fi

几个会让人误判的地方

时区。事件在库里以 UTC 存,wp cron event list 按站点时区显示。拿它跟服务器日志对时间,先确认 date 和站点时区不是两套。探针里用 gmdate() 写 UTC,比较起来最省事。

CLI 的 PHP 和 FPM 的 PHP 不是一回事。版本、扩展、php.ini 都可能是两套。先 wp --info 看清楚用的是哪个二进制,别在浏览器里测得好好的,定时任务里报找不到扩展。

关掉了却没配系统 cron。这是最常见的一种:DISABLE_WP_CRON 写着,系统 cron 忘了加,所有定时任务静默停摆,表现就是「插件说上次备份是 N 天前」。上线前 grep 一遍 wp-config.php。

多台 web 节点。每台都跑系统 cron 且没有共享锁时,同一条到点的事件可能被跑两次。要么只让一台跑,要么在跑之前抢一个分布式锁(Redis 之类),别指望 NFS 上的 flock。

长时间任务撑满窗口。一条跑 20 分钟的事件会把后面所有事件一起拖后。把大任务拆小,或者单独给它一个低频 timer,用 wp cron event run <hook> 精确指定。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • WP-Cron 不是一个常驻的调度器
  • 什么情况下该换成系统 cron
  • 换过去的三步
    • 关掉请求触发的 spawn
    • 用站点文件所有者的身份跑
    • 长任务用 systemd timer 更好排错
  • 这套配置也可以直接买人做
  • 怎么确认任务真的跑了
  • 几个会让人误判的地方
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档