停用 WP-Cron,改用 system cron 執行排程
WP-Cron 只有有人載入頁面才會執行,安靜網站會延遲、忙碌網站會堆積工作。使用 WP-CLI 移交給 system cron,並確認排程確實執行。
WP-Cron 的用途,以及 system cron 為何能取代它
WP-Cron 是 WordPress 內建的工作排程器,只有在有人請求頁面時才會執行。WordPress 不會自行喚醒並執行工作。每次收到未由快取提供的請求時,WordPress 都會讀取排程工作清單;如果有工作已到期,便會在 /wp-cron.php 對自己發出第 2 個 HTTP 請求來執行該工作。將這項工作交給 system cron 後,便能依固定排程穩定執行,不論該分鐘有 1000 名訪客或完全沒有訪客。
實際執行工作的只有 2 行:wp-config.php 中的常數,以及 crontab 項目。本指南其餘內容,都是說明這 2 行未涵蓋的部分。工作必須以哪個使用者身分執行、如何確認排程事件確實執行,以及設定在網站上不顯示任何錯誤時可能失敗的 3 種方式。
範例使用 /srv/www/example.com 作為 WordPress 目錄,並使用 www-data 作為網頁伺服器使用者。請將所有路徑與使用者替換為你自己的設定。
忙碌網站上,哪位訪客觸發了 cron 成本
每個未命中快取的請求都必須支付這項檢查的成本。WordPress 載入 cron 選項、比較時間戳記,若有工作到期,便呼叫 spawn_cron(),向 /wp-cron.php 發送非阻塞的回送請求。訪客不會等待結果,但 PHP worker 會等待。在使用 PHP-FPM 搭配 pm.max_children = 5 的小型 VPS 上,一個執行緩慢的排程工作可能會占用五分之一的 PHP 容量,直到工作完成為止。而且它最可能在流量最高的那 1 分鐘內被觸發,因為此時載入頁面的次數最多。
WordPress 會限制重複執行。它會取得一個存活時間為 WP_CRON_LOCK_TIMEOUT 的鎖定,預設為 60 秒,因此同時到達的訪客不會各自啟動一次執行。這個鎖定能限制重複工作,但不會將工作移出請求路徑。
在判斷這項成本是否重要之前,先計算它在自己的伺服器上觸發的頻率。每個回送請求都會出現在網頁伺服器的存取日誌中:
sudo grep -c 'wp-cron.php' /var/log/nginx/access.logApache 則會寫入 /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 上掛接 WordPress 的 cron 檢查。若在該 require 之後定義常數,設定時間就太晚,無法產生任何作用。檔案看似正確,但觸發程序仍會持續執行。
確認該行位於預期位置:
grep -n "DISABLE_WP_CRON\|stop editing" /srv/www/example.com/wp-config.php這個常數不會停止排程事件。外掛仍會像之前一樣,持續將工作加入佇列。它只會阻止頁面載入時執行該佇列,因此在完成步驟 3 前,佇列不會再自動執行。
它也不會阻擋對 /wp-cron.php 的直接請求。任何人仍可請求該 URL。這通常沒有問題,因為該檔案只會執行已到期的工作。是否在 Web 伺服器設定中封鎖它由你決定。若確實封鎖,本文末尾附近的 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 --infophp 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 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 會取得鎖定檔;如果先前的執行程序仍持有該鎖定,便立即結束。/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 會以自己的 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.logWP-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 計時器替代方案
如果主機上其他排程工作已經以 systemd 服務與計時器執行,也將 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.targetsudo 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 20systemd 不會同時執行同一服務的兩個副本,因此這個版本不需要 flock。Persistent=true 會在主機關機期間錯過執行時,補執行該次任務;crontab 項目無法做到這點。
請選擇使用 crontab 或計時器。若兩者同時針對同一個網站執行,佇列就會被處理兩次,電子郵件或訂單工作重複執行時,客戶會看到異常。
為何 cron 工作不得以 root 執行
這是設定失敗的 3 種方式中的第 1 種。將工作加入 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-contentroot 的 crontab 與 www-data 的 crontab 是不同檔案,因此從其中一個檔案刪除該行,不會影響另一個檔案。請檢查兩者:
sudo crontab -u root -l
sudo crontab -u www-data -lcron 回報 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。
為什麼 1 分鐘間隔會重現原本的問題
這是第三種故障。* * * * * 看似比 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 新增的快取感知工作配置,也只能決定程序使用哪個核心,無法限制你啟動的程序數量。
請根據實際依賴的最短排程選擇間隔,並先測量一次執行時間:
time sudo -u www-data /usr/local/bin/wp --path=/srv/www/example.com cron event run --due-now5 分鐘是合理的預設值:排程在 09:00 發布的文章最晚會在 09:05 發布。若網站沒有任何時間敏感的工作,使用 15 分鐘也沒有問題。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 request timeout 限制,因此長時間工作可能在中途被中止。
- 憑證必須有效,否則
curl會以SSL certificate problem停止,因此請透過 在 nginx 上使用 Certbot 確保憑證持續續期。 - Page caching 不得快取
wp-cron.php,否則 cron 請求會取得快取回應,導致沒有任何工作執行。 - 你不會取得每個事件的輸出,因此工作是否執行過,只能從其產生的結果判斷。
-sS 會在成功時讓 curl 保持安靜,但仍會輸出錯誤訊息,適合用於 cron job。
伺服器的排程還應包含哪些工作
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 擁有的檔案,之後 web server 無法寫入。
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,還是對 wp-cron.php 執行 curl 就足夠?
curl 可以使用;無法安裝 WP-CLI 時,這是合適的做法。它的速度較慢,因為會透過 web server 載入 WordPress,而且會受到請求逾時限制。WP-CLI 會在命令列 PHP 程序中執行事件,不受 web 逾時限制,並逐一輸出事件及其執行時間。因此,log 能明確顯示執行了哪些事件,以及各事件耗時多久。