一句话结论:WP-Cron 不是系统级的真 cron,它靠”有人访问站点”触发,流量小的站点定时任务会不准时甚至不执行。生产环境的标准解法是在 wp-config.php 里加 define('DISABLE_WP_CRON', true),再用服务器 crontab 每分钟拉一次 wp-cron.php 。下面把调度、自定义间隔、时区和排错一次讲清。
一、 WP-Cron 到底怎么跑的
WP-Cron 的工作方式是:每次页面加载时,WordPress 检查 cron 数组里有没有到期的任务,有就发一个内部请求去执行。这意味着——没人访问就没有触发;访问量大时又可能并发触发。官方插件手册明确提示:如果你的任务很吃资源,默认”等用户来访问再跑”并不合适,用户可能会对着白屏干等。
| 对比项 | WP-Cron(默认) | 系统 Cron + DISABLE_WP_CRON |
|---|---|---|
| 触发方式 | 页面访问触发 | 服务器定时触发 |
| 准时性 | 低流量站点可能延迟很久 | 分钟级精确 |
| 资源占用 | 占用访客请求进程 | 独立进程,不影响前端 |
| 适用场景 | 本地开发、小博客 | 生产环境、定时发布、同步任务 |
二、正确注册一个定时任务
注册定时任务分三步:加钩子、判重、再调度。漏掉判重是最常见的坑——wp_schedule_event() 每次调用都会新增一条,如果放在每次页面加载都会执行的代码里,几小时就能攒出几千条重复任务。
- 用 add_action 把自定义钩子和真正的执行函数绑起来:add_action(‘my_daily_hook’, ‘my_daily_task’);
- 用 wp_next_scheduled(‘my_daily_hook’) 判断任务是否已存在,不存在才调度,避免重复堆积。
- 用 wp_timezone() 构造”站点时区今天 9:00″的 DateTime,再 getTimestamp() 拿到 UTC 时间戳作为首次执行时间。
- 调用 wp_schedule_event($timestamp, ‘daily’, ‘my_daily_hook’) 完成注册。
- 在插件的 register_deactivation_hook 里用 wp_unschedule_event 把任务清掉,停用后不残留。
关键代码
// 1. 绑定钩子
add_action( 'my_daily_hook', 'my_daily_task' );
// 2. 按站点时区算出"今天 9:00"
function my_next_9am() {
$tz = wp_timezone();
$next = new DateTime( 'today 09:00', $tz );
if ( $next <= new DateTime( 'now', $tz ) ) {
$next->modify( '+1 day' );
}
return $next->getTimestamp();
}
// 3. 判重后调度
if ( ! wp_next_scheduled( 'my_daily_hook' ) ) {
wp_schedule_event( my_next_9am(), 'daily', 'my_daily_hook' );
}
// 4. 停用时清理
register_deactivation_hook( __FILE__, 'my_deactivate' );
function my_deactivate() {
$ts = wp_next_scheduled( 'my_daily_hook' );
if ( $ts ) {
wp_unschedule_event( $ts, 'my_daily_hook' );
}
}
三、时区这个坑
WordPress 在启动时会把 PHP 默认时区强制设为 UTC 。所以 wp_schedule_event( strtotime('today 9am'), 'daily', ... ) 看起来对,实际建的是 UTC 9 点,落到中国时区就是下午 5 点,夏令时地区还会再漂。
正确做法是走 wp_timezone()(WP 5.3+)拿到后台”设置 → 常规”里配的时区对象,用 DateTime 构造本地时间,再 getTimestamp()——时间戳本身永远基于 UTC,偏移和夏令时都被正确处理了。
为什么不要用 date_default_timezone_set() 绕过?
四、自定义执行间隔
默认只有 hourly 、 twicedaily 、 daily 三档(外加一次性任务)。要更细的粒度,用 cron_schedules 过滤器加:
add_filter( 'cron_schedules', function ( $schedules ) {
$schedules['per_minute'] = [
'interval' => 60,
'display' => '每分钟',
];
return $schedules;
} );
注意顺序:必须先加 cron_schedules 过滤器,再调用 wp_schedule_event,否则自定义周期还没注册,调度会失败。
五、生产环境接入系统 Cron
- 编辑 wp-config.php,加入 define(‘DISABLE_WP_CRON’, true); 关掉页面触发。
- SSH 登录服务器执行 crontab -e,加入:*/5 * * * * /usr/bin/php -q /站点路径/wp-cron.php >/dev/null 2>&1(PHP 路径以实际为准)。
- 如需限制并发,可在 wp-config.php 加 define(‘WP_CRON_LOCK_TIMEOUT’, 60); 保证 60 秒内不会重复跑。
- 用 wp cron event list(WP-CLI)确认任务已注册,观察一两个周期确认按时触发。
如果服务器不方便开 crontab,还有两个备选:宝塔/AMH 面板的”计划任务”里选 Shell 执行;或用外部监控服务定时请求 https://你的域名/wp-cron.php?doing_wp_cron。前者更稳定,后者依赖第三方可用性。
六、任务不执行的排查清单
- 站点开了全页面缓存且缓存规则拦掉了 wp-cron.php 请求;
DISABLE_WP_CRON已开启但忘记配系统 crontab(最常见的自摆乌龙);- 任务回调里抛了致命错误,被静默吞掉——查 PHP error log;
- 插件停用没清理,cron 数组里堆了几千条僵尸任务,拖慢每次加载;
- 对象缓存(Redis/Memcached)接管后,cron 数组本身被缓存住了,改完没生效。
需要远程管理站点任务时,可以配合 WordPress REST API 进阶用法 做自定义端点;站点性能相关的排查另见 WordPress 速度优化实测。
查看官方 WP-Cron 文档







评论 (0)