SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

WordPress 停用 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 發送非阻塞的 loopback 請求。訪客不會等待結果,但 PHP worker 會。在使用 PHP-FPM 與 pm.max_children = 5 的小型 VPS 上,一個執行緩慢的排程工作可能會長時間占用五分之一的 PHP 處理能力。而且它最可能在網站最繁忙的一分鐘內被觸發,因為此時的頁面載入次數最多。

WordPress 會限制重複執行。它會取得一個生命週期為 WP_CRON_LOCK_TIMEOUT 的鎖定,預設為 60 秒,因此同時到達的訪客不會各自啟動一次執行。這個鎖定只能限制重複執行,無法將工作移出請求處理流程。

在判斷這項成本是否重要之前,先計算它在自己的伺服器上觸發的頻率。每個 loopback 請求都會出現在網頁伺服器的存取日誌中:

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 會將 cron 檢查掛接到 init。若在該 require 之後定義常數,設定就太晚,無法產生作用;檔案看似正確,但觸發程序仍會執行。

確認該行位於預期位置:

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

這個常數不會停止排程事件。外掛仍會照常將工作加入佇列。它只會阻止頁面載入時執行該佇列,因此在完成步驟 3 前,佇列將完全不會執行。

它也不會封鎖對 /wp-cron.php 的直接請求。任何人仍可請求該 URL。這通常沒有問題,因為該檔案只會執行到期的工作。是否在 web server 設定中封鎖它,由你自行決定。若封鎖,這份指南接近結尾處的 curl fallback 也會停止運作。

步驟 2:安裝 WP-CLI

WP-CLI 是 WordPress 官方的命令列工具。它需要 PHP 命令列二進位檔,這是與網頁伺服器 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 版本。若這 3 項都能顯示,表示 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)。若出現未定義常數的 fatal error,表示尚未執行到 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 stack 進行每個網站的設定時就是這種情況,請在以下所有步驟使用該使用者。

建立該使用者可寫入的日誌目錄:

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 會取得 lock file;如果先前的執行程序仍持有該檔案,就會立即放棄。/usr/local/bin/wp 是絕對路徑,cron 必須使用絕對路徑。--path 讓命令可從任何工作目錄執行。--due-now 只會執行已到時間的事件,而不是佇列中的所有事件。重新導向會將一般輸出與錯誤寫入同一個檔案,方便您讀取。

實務上,這項重新導向不可省略。Cron 會將工作的輸出寄送給該使用者,但大多數 VPS image 都未安裝 mail transfer agent,因此 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 會以自己的 unit 將日誌寫入 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 名稱上註冊 callback,因此執行它不會對網站做其他處理。如果兩個間隔後該 hook 仍列在清單中,表示佇列未被執行;前兩項檢查可協助判斷問題出在 cron 還是 WP-CLI。

不要使用 wp cron test 進行這項檢查。該命令會檢查由訪客觸發的啟動機制是否正常;當 DISABLE_WP_CRON 為 true 時,它會回報錯誤。在正確設定的伺服器上,這個錯誤是預期輸出,不代表發生故障。

systemd timer 替代方案

如果主機上的其他排程工作已經透過 systemd 服務與 timer 執行,也應將 WordPress 加入其中。如此一來,每次執行都會記錄在 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 不會同時執行同一服務的兩個執行個體,因此這個版本不需要 flockPersistent=true 會補執行主機關機期間錯過的排程,而 crontab 項目無法做到這點。

請選擇使用 crontab 或 timer。若同一個網站同時使用兩者,佇列就會被處理兩次,電子郵件或訂單工作重複執行時,客戶會直接看到結果。

為何 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 錯誤,或由 kernel 終止 PHP 以回收記憶體。你可以使用 sudo dmesg -T | grep -i 'killed process' 確認這項情況。

WP-CLI 會直接執行事件 callback,而不是提出 wp-cron.php 請求,因此 WordPress 用來避免重複啟動的 60 秒鎖定在此不適用。第 3 步驟項目中的 flock -n 現在負責避免重疊執行。依設計,略過的執行會立即且靜默地結束。

請根據實際依賴的最短排程選擇間隔,並先測量單次執行時間:

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 分鐘也很適合。只有商店和由佇列驅動、確實需要此頻率的 plugin 才應使用 1 分鐘間隔,而且前提是你已確認單次執行能在幾秒內完成。

如果無法安裝 WP-CLI

有些主機會封鎖 shell 工具。對 wp-cron.php 發出一般 HTTP 請求,也會執行相同的佇列,只是會經過完整的 Web stack:

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

明確來說,這種方式會失去以下功能:

  • 執行時間會受到 Web server 與 PHP-FPM 請求逾時限制,因此長時間工作可能在中途被終止。
  • 憑證必須有效,否則 curl 會以 SSL certificate problem 終止。因此,請透過 在 nginx 上使用 Certbot 確保憑證能正常續期。
  • 頁面快取不得快取 wp-cron.php,否則 cron 請求會取得快取回應,導致任何工作都不會執行。
  • 每個事件都不會輸出結果,因此只能透過工作造成的效果,判斷工作是否曾經執行。

成功時,-sS 會讓 curl 保持靜默,但仍會輸出錯誤。這正適合用於 cron 工作。

伺服器排程還應包含哪些工作

system cron 接管 WordPress 佇列後,請將其餘伺服器的例行工作也集中在同一處管理,方便檢視。作業系統安全性修補應交由 unattended upgrades 處理,而不是自行維護的 cron 設定行。WordPress 外掛與佈景主題更新則需另外決定:在 crontab 中加入 wp plugin update --all,可能會在凌晨 3 點無人監看時直接破壞正式網站,因此應以明確的流程執行,或先經過 staging 步驟並建立備份。

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 擁有的檔案,之後網頁伺服器將無法寫入這些檔案。

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,這是適當的做法。它的速度較慢,因為會透過網頁伺服器載入 WordPress,且會受到請求逾時限制。WP-CLI 會在命令列 PHP 程序中執行事件,不受網頁逾時限制,並為每個事件輸出一行及其執行時間,因此日誌能明確顯示執行了哪些事件,以及各自花費多久。