SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-23

如何禁用 wp-cron 并改用 system cron

WP-Cron 仅在有人访问页面时运行,安静网站会延迟,繁忙网站会堆积负载。使用 WP-CLI 配置 system cron,并验证计划任务确实执行。

什么是 wp-cron,以及为什么使用 system cron 替代它

WP-Cron 是 WordPress 内置的任务调度器,但它只有在有人请求页面时才会运行。WordPress 不会自行唤醒。在每个未从缓存提供响应的请求中,WordPress 都会读取计划任务列表;如果某个任务已到执行时间,它会向 /wp-cron.php 发起第二个 HTTP 请求,以执行该任务。将此任务移交给 system cron 后,任务会按固定计划稳定运行,无论这一分钟内网站有 1000 个访问者,还是没有访问者。

真正执行任务的只有两行配置:wp-config.php 中的一个常量,以及一条 crontab 条目。本指南的其他内容,都是这两行配置无法说明的部分:任务必须以哪个用户身份运行,如何确认计划事件确实已执行,以及配置在不向网站输出任何信息的情况下失败的 3 种方式。

示例使用 /srv/www/example.com 作为 WordPress 目录,使用 www-data 作为 Web 服务器用户。请在所有位置替换为您自己的路径和用户。

繁忙网站上是哪个访客触发了 cron 负载

每个未命中缓存的请求都要执行一次检查。WordPress 会加载 cron 选项,比较时间戳;如果有任务到期,就调用 spawn_cron(),向 /wp-cron.php 发送非阻塞回环请求。访客不会等待结果,但 PHP worker 会等待。在运行 PHP-FPM 和 pm.max_children = 5 的小型 VPS 上,一个缓慢的计划任务可能在其运行期间占用五分之一的 PHP 处理能力。而且它最可能在流量最高的那一分钟被触发,因为此时页面加载次数最多。

WordPress 会限制重复执行。它会获取一个生命周期为 WP_CRON_LOCK_TIMEOUT 的锁,默认是 60 秒,因此同时访问的访客不会分别启动一次执行。该锁只能限制重复执行,不能将任务移出请求处理路径。

在决定它是否重要之前,先统计它在您自己的服务器上触发的频率。每次回环请求都会出现在 Web 服务器访问日志中:

sudo grep -c 'wp-cron.php' /var/log/nginx/access.log

Apache 则会写入 /var/log/apache2/access.log。每天数千次调用会产生实际开销。您应在自己的服务器上测量这个数字,而不是直接采用文章中的数据,就像在进行其他更改前后 对 VPS 进行基准测试 一样。

缓存会改变结果。如果页面缓存将大多数请求作为静态 HTML 提供,PHP 就不会处理这些请求,因此也不会执行 cron 检查。缓存率很高的繁忙网站,其行为会接近下面的安静网站。

安静网站上的哪位访客触发了 cron

没有访客,就不会运行 cron。每天只有少量访问的网站,其计划任务每天也只会运行几次,运行时刻取决于这些访问随机到达的时间。

症状基本相同。计划在 09:00 发布的文章会一直留在文章列表中,并标记为 Missed schedule,直到有人加载页面。备份插件会跳过夜间备份。更新检查会延迟,因此仪表盘显示没有可用更新,但安全版本实际上已经发布。订单邮件、续订通知和到期警告也会延迟发送。

日志中不会记录任何错误。在 WordPress 看来,任务并没有延迟,因为它根本没有启动。

步骤 1:在 wp-config.php 中关闭访客触发器

打开 /srv/www/example.com/wp-config.php,并添加以下常量:

define( 'DISABLE_WP_CRON', true );

将其放在内容为 /* That's all, stop editing! Happy publishing. */ 的行上方,因为该注释下方的行需要 wp-settings.php,而 wp-settings.php 会在 init 上挂接 cron 检查。若在该 require 之后定义常量,设置时间就太晚,无法产生任何效果;文件看起来配置正确,但触发器仍会运行。

确认该行位于预期位置:

grep -n "DISABLE_WP_CRON\|stop editing" /srv/www/example.com/wp-config.php

该常量不会停止事件调度。插件仍会像之前一样向队列添加任务。它只会阻止页面加载处理该队列,因此在完成步骤 3 之前,队列将不再运行。

它也不会阻止对 /wp-cron.php 的直接请求。任何人仍可请求该 URL;通常这不会造成问题,因为该文件只会执行已到期的任务。在 Web 服务器配置中阻止该 URL 是可选的。如果确实阻止,本文末尾附近的 curl 回退机制也会停止工作。

步骤 2:安装 WP-CLI

WP-CLI 是 WordPress 的官方命令行工具。它需要 PHP 命令行二进制文件,该文件与 Web 服务器使用的 PHP 模块属于不同的软件包。

php -v
sudo apt install -y php-cli

从 phar 构建安装 WP-CLI。这也是官方安装指南推荐的方式:

cd /tmp
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --info

php wp-cli.phar --info 会输出 PHP 二进制文件路径、PHP 版本和 WP-CLI 版本。如果这三项都能输出,说明 phar 可以正常工作。截至 2026 年 8 月,安装指南要求的最低 PHP 版本为 7.2.24,而 Ubuntu 24.04 自带 PHP 8.3,因此当前服务器的版本远高于该要求。之后使用 sudo wp cli update 更新。

以网站所属用户运行 WP-CLI,绝不要使用 root:

sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com core version

使用 root 时,WP-CLI 会拒绝启动:

Error: YIKES! It looks like you're running this as root.

它会建议使用 --allow-root。这里不要这样做。原因见下方的第一个故障模式。

还要注意,sudo -u www-data -i 无法正常工作,因为该账户的登录 shell 是 /usr/sbin/nologin,执行后会得到 This account is currently not available.。将命令直接传给 sudo -u 可以绕过登录 shell,因此能够正常运行。

现在确认 WordPress 本身可以读取第 1 步中定义的常量:

sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com eval 'var_dump( DISABLE_WP_CRON );'

该命令会输出 bool(true)。如果出现“未定义常量”的致命错误,说明程序没有执行到 define() 行,通常是因为该行被放在了 require 语句之后。

第 3 步:以正确的用户添加 cron 条目

正确的用户是拥有 PHP 写入文件的用户。检查两端:

stat -c '%U %G' /srv/www/example.com/wp-content/uploads
grep -E '^(user|group) = ' /etc/php/8.3/fpm/pool.d/www.conf

在默认 Ubuntu 安装中,两者都会显示为 www-data。如果您为站点配置了使用独立用户的 PHP-FPM pool,那么在 Ubuntu 24.04 上的 LAMP 堆栈中进行按站点配置时通常就是这种情况。下面的所有操作都使用该用户。

创建该用户可写入的日志目录:

sudo install -d -o www-data -g www-data -m 750 /var/log/wp-cron

编辑该用户的 crontab:

sudo crontab -u www-data -e

添加一行:

*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1

逐项说明。*/5 每 5 分钟运行一次。flock -n 获取锁文件;如果上一次运行仍持有该锁,则立即退出。/usr/local/bin/wp 是绝对路径,cron 需要使用绝对路径。--path 使命令可以从任意工作目录运行。--due-now 只运行到达执行时间的事件,而不是队列中的所有事件。重定向会将标准输出和错误输出写入同一个文件,方便查看。

在实际使用中,不能省略此重定向。Cron 会将任务输出通过邮件发送给任务用户,而大多数 VPS 镜像都未安装邮件传输代理。随后 cron 会记录 (CRON) info (No MTA installed, discarding output),并丢弃这些输出。写入文件可以保留故障证据。

检查文件是否已保存:

sudo crontab -u www-data -l

如果有多个站点,请为每个站点使用一行,并错开执行分钟数,避免它们同时启动:

*/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-one.lock /usr/local/bin/wp --path=/srv/www/one.example.com cron event run --due-now >> /var/log/wp-cron/one.log 2>&1
2-59/5 * * * * /usr/bin/flock -n /run/lock/wp-cron-two.lock /usr/local/bin/wp --path=/srv/www/two.example.com cron event run --due-now >> /var/log/wp-cron/two.log 2>&1

日志会无限增长,除非对其进行轮换。写入 /etc/logrotate.d/wp-cron:

/var/log/wp-cron/*.log {
    weekly
    rotate 4
    missingok
    notifempty
    compress
    create 640 www-data www-data
}

检查语法解析是否正确,但不要执行任何操作:sudo logrotate --debug /etc/logrotate.d/wp-cron。

第 4 步:确认计划事件确实已执行

成功保存 crontab 行并不能证明任何事情。应从成本最低的检查开始,逐步确认到最终结果。

首先,cron 是否启动了该命令?cron 会使用自己的单元将日志写入 journal:

journalctl -u cron.service --since "15 min ago" | grep wp

正常的日志项如下,前面的时间戳和主机名已省略:

CRON[24913]: (www-data) CMD (/usr/bin/flock -n /run/lock/wp-cron-example.lock /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now >> /var/log/wp-cron/example.log 2>&1)

这表示 cron 已以 www-data 身份启动命令。但这不能说明命令是否执行成功。

其次,WordPress 是否执行了相关操作?读取日志文件:

sudo tail -n 20 /var/log/wp-cron/example.log

WP-CLI 会为每个事件输出一行,最后输出总数:

Executed the cron event 'wp_version_check' in 0.418s.
Success: Executed a total of 2 cron events.

错误也会写入同一个文件,这正是使用 2>&1 的原因。大多数运行不会有到期事件,因此写入内容很少。应在确认有待处理任务的一次运行后读取该文件。

第三,进行端到端验证。安排一个标记事件,然后观察它消失:

sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event schedule wp_cli_cron_check now
sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event list --fields=hook,next_run_relative --format=csv | grep wp_cli_cron_check

等待一个时间间隔,然后再次运行列表命令。该 hook 已消失,因为一次性事件执行后会从队列中删除。没有插件为该 hook 名称注册回调,因此执行它不会对站点进行其他操作。如果等待两个时间间隔后该 hook 仍在列表中,说明队列没有运行。前两项检查可以帮助判断问题出在 cron 还是 WP-CLI。

不要为此使用 wp cron test。该命令用于检查由访问者触发的生成机制是否正常;当 DISABLE_WP_CRON 为 true 时,它会报错。在配置正确的服务器上,该错误是预期输出,不表示存在故障。

systemd 定时器替代方案

如果服务器上的其他定时任务已经通过 systemd 服务和定时器运行,也将 WordPress 放到 systemd 中。这样每次运行都会记录在 systemctl list-timers 中,输出也会写入 journal,而不必写入需要轮换的文件。

编写 /etc/systemd/system/wp-cron-example.service:

[Unit]
Description=Run due WordPress cron events for example.com

[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now

然后编写 /etc/systemd/system/wp-cron-example.timer:

[Unit]
Description=Run WordPress cron for example.com every 5 minutes

[Timer]
OnCalendar=*:0/5
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now wp-cron-example.timer
systemctl list-timers wp-cron-example.timer
journalctl -u wp-cron-example.service -n 20

systemd 不会同时运行同一服务的两个实例,因此此方案不需要 flock。Persistent=true 会补执行服务器关机期间错过的任务,而 crontab 条目无法实现这一点。

请选择 crontab 或定时器中的一种。对同一站点同时运行两者,会导致队列被处理两次;电子邮件或订单任务重复执行后,客户可以直接看到结果。

为什么 cron 任务不得以 root 身份运行

这是该设置失败的三种情况中的第一种。将任务放入 root 的 crontab 后,WP-CLI 会在执行任何操作前停止:

Error: YIKES! It looks like you're running this as root.

队列不会运行。如果未重定向输出,您也不会看到这条消息。危险的修复方式是添加 --allow-root,因为这样插件在本次运行期间写入的所有文件都将归 root 所有。下一次 Web 请求以 www-data 身份运行时,无法写入这些目录,网站就会开始报告以下错误:

Unable to create directory wp-content/uploads/2026/08. Is its parent directory writable by the server?

修复文件所有权,然后移动任务:

sudo chown -R www-data:www-data /srv/www/example.com/wp-content

root 的 crontab 与 www-data 的 crontab 是两个独立的文件,因此从其中一个文件删除该行不会影响另一个文件。请检查两者:

sudo crontab -u root -l
sudo crontab -u www-data -l

cron 为什么报告 wp: not found

这是第二个故障。Cron 为用户任务提供的 PATH 很短,/usr/bin:/bin。WP-CLI 安装在 /usr/local/bin 中,而该路径不在列表内。任务启动后会在几分之一秒内失败,日志中只留下一行:

/bin/sh: 1: wp: not found

不要猜测,直接查看 cron 的环境。添加一行临时配置:

*/5 * * * * env > /tmp/cron-env.txt 2>&1

等待一个间隔后读取 /tmp/cron-env.txt,然后删除该行。该文件中的 PATH= 值就是任务实际获取的值。

有两种修复方法。使用绝对路径 /usr/local/bin/wp,如第 3 步所示。或者在 crontab 顶部、所有任务行之前统一设置 PATH:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

下一层还存在相同的问题。wp phar 文件以 #!/usr/bin/env php 开头,因此 shell 也必须能够找到 php。如果 PHP 位于 /usr/bin 之外,定制构建版本和控制面板构建版本中经常会出现这种情况,则会得到:

/usr/bin/env: 'php': No such file or directory

此时应显式调用解释器,例如 /usr/local/bin/php /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now。

为什么每分钟运行会重现原始问题

这是第三个故障。* * * * * 看起来比 5 分钟更安全,但在繁忙网站上,它会让问题回到原点。如果一次运行耗时超过间隔,下一次运行会在上一次仍未结束时启动。10 分钟后,系统中会有 10 个 PHP 进程。每个进程都占用自己的内存,并建立自己的数据库连接。

直接检查是否发生堆积:

ps -eo etimes,user,args | grep '[c]ron event run'

etimes 是进程运行时间,单位为秒。只有一行表示正常。若有多行的运行时间远高于设定间隔,说明任务正在堆积。在配置较小的 VPS 上,最终可能出现 MySQL Too many connections 错误,或者内核为回收内存而终止 PHP 进程。可使用 sudo dmesg -T | grep -i 'killed process' 确认这一点。

WP-CLI 会直接运行事件回调,而不是请求 wp-cron.php,因此 WordPress 用于防止重复生成任务的 60 秒锁在这里不适用。第 3 步条目中的 flock -n 现在负责防止任务重叠。跳过的运行会立即静默退出,这是设计行为。重叠问题需要在 crontab 中解决,主机内核不会替您处理:即使是 Linux kernel 7.2 中新增的缓存感知任务放置机制,也只会决定进程运行在哪个 CPU 核心上,不会限制您启动的进程数量。

应根据实际依赖的最短计划选择间隔,并先测量一次运行时间:

time sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now

5 分钟是合理的默认值:计划在 09:00 发布的文章会在 09:05 前发布。对于没有时间敏感任务的网站,15 分钟也足够。1 分钟间隔适用于确实需要高频运行的商店和队列驱动插件,但前提是您已确认每次运行只需几秒。

如果无法安装 WP-CLI

某些主机会阻止使用 shell 工具。向 wp-cron.php 发送普通 HTTP 请求会运行同一个队列,但请求会经过完整的 Web 堆栈:

*/5 * * * * /usr/bin/curl -sS --max-time 120 "https://example.com/wp-cron.php?doing_wp_cron" > /dev/null

需要明确接受的限制:

  • 运行时长受 Web 服务器和 PHP-FPM 请求超时限制,因此长任务可能会在执行过程中被中断。
  • 证书必须有效,否则 curl 会因 SSL certificate problem 停止运行。因此,请通过 在 nginx 上使用 Certbot 确保续期正常。
  • 页面缓存不能缓存 wp-cron.php,否则 cron 请求会获得缓存响应,任务不会执行。
  • 不会输出每个事件的结果,因此只能通过任务产生的效果确认其是否运行。

-sS 在成功时保持 curl 静默,但仍会输出错误。这正适合在 cron 任务中使用。

服务器计划任务中还应包含哪些内容

system cron 接管 WordPress 队列后,将服务器其余的日常任务也集中放在同一处,便于统一查看。操作系统安全补丁应交由 无人值守升级 处理,而不是由您手动维护 cron 条目。WordPress 插件和主题更新需要单独决定:在 crontab 中运行 wp plugin update --all 可能会在凌晨 3 点无人监控时直接破坏线上站点,因此应有计划地执行,或先经过预发布环境验证并完成备份。

FAQ

禁用 WP-Cron 会阻止计划文章发布吗?

不会,前提是由其他机制运行队列。DISABLE_WP_CRON 只会阻止页面加载触发队列。事件仍会按原计划调度。设置为 09:00 发布的文章,会在 09:00 之后首次运行 cron 时发布,因此每五分钟运行一次时,最迟会在 09:05 发布。如果设置了该常量却从未添加 cron 条目,文章会一直留在列表中,并标记为 Missed schedule,直到有机制运行队列。

WordPress cron 作业应由哪个用户运行?

应由拥有 PHP 写入文件权限的用户运行。在默认 Ubuntu 安装中,该用户是 www-data。使用 stat -c '%U %G' /srv/www/example.com/wp-content/uploads 检查该用户,并将其与 PHP-FPM pool 配置中的 user = 行进行比较。以 root 运行该作业会使 WP-CLI 停止并显示 YIKES 错误;使用 --allow-root 强制运行,则会在 wp-content 中留下由 root 所有的文件,Web 服务器之后无法写入这些文件。

system cron 应多久运行一次 WordPress cron?

每五分钟运行一次适合大多数站点。将间隔设置为你实际依赖的最短计划间隔,并确保该间隔明显长于单次运行所需的时间。可以在 WP-CLI 命令前加上 time 来测量运行时间。在繁忙站点上,如果没有 flock 防止并发运行,每分钟运行一次会导致多个任务相互叠加。

禁用 WP-Cron 后,为什么 wp cron test 会失败?

因为该命令用于测试由访客触发的任务启动机制,而 DISABLE_WP_CRON 设置为 true 时,它会报告错误。对于这种配置的服务器,这是正确结果。请改为检查 system cron 路径:读取 /var/log/wp-cron/example.log,或使用 wp cron event schedule 调度一个标记事件,并在下一次运行后确认该事件已从 wp cron event list 中消失。

必须使用 WP-CLI 吗?使用 curl 请求 wp-cron.php 是否足够?

curl 可以工作。在无法安装 WP-CLI 时,这是合适的方案。但它的速度较慢,因为它会通过 Web 服务器加载 WordPress,并且受请求超时限制。WP-CLI 会在命令行 PHP 进程中运行事件,不受 Web 超时限制;它还会为每个事件输出一行及其持续时间,因此日志可以准确显示运行了哪些事件,以及每个事件耗时多久。