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

cron 工作不執行?5 個常見原因與排查方法

cron 工作看似完全沒執行,可能是 PATH 過短、% 未跳脫、使用錯誤的 crontab、輸出寄到郵件,或指令碼依賴登入 shell。

為什麼 cron 工作沒有執行

幾乎所有「完全沒有執行」的 cron 工作其實都曾經執行過。它在不同於您 shell 的環境中執行,於第一秒就失敗,而訊息則被送到您未查看的位置。以下 5 個原因幾乎可以解釋所有相關問題:搜尋路徑、百分比符號、錯誤的 crontab 檔案、輸出被寄送到郵件,以及指令碼預期存在登入工作階段。

cron 是一個 daemon(背景服務),會讀取 crontab 檔案,並依排程啟動命令。它不會讀取您的 .bashrc,不會開啟終端機,不會啟動 login shell,也不會在命令失敗時通知您。以下每個原因都源自這 4 項事實。

請依序檢查,並先回答所有問題的核心:cron 是否真的觸發了工作?「cron 從未啟動工作」與「工作已啟動但隨即終止」是不同問題,兩者沒有共同原因,因此請先確認這一點。

cron 是否實際執行?

不同發行版家族使用不同的 daemon unit 名稱。請檢查兩者,再讀取日誌。

systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"

Debian 和 Ubuntu 將此 unit 稱為 cron。Fedora、Rocky 和 Alma 將其稱為 crond。同一台機器只會存在其中一個名稱,因此兩個命令中有一個回報 unknown unit 屬於正常情況,不是故障。

請讀取系統自行寫入的項目。不要尋找指南中複製的特定行,因為不同 cron 實作和日誌設定的文字可能不同。你只需檢查兩件事:指定排程的那一分鐘是否有項目,以及該項目是否包含你的命令。若項目包含你的命令,表示 cron 已完成自身工作,故障位於命令內部。完全沒有項目表示 cron 從未取得你的排程,原因是下方的第 3 項。

部分映像會透過 rsyslog 將 cron 訊息寫入檔案,而不是寫入 journal。請在 /var/log 中尋找以 cron 或 syslog 命名的檔案,然後讀取檔案末端的內容。

ls -l /var/log
sudo tail -n 50 /var/log/syslog

如果 unit 和日誌都不存在,可能只是尚未安裝 cron。精簡的 cloud image 和容器通常不會預先安裝它。

dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cron

原因 1:cron 沒有您的 PATH

互動式 shell 會從 /etc/profile~/.profile~/.bashrc 及這些檔案所載入的內容建立 PATH。cron 工作不會執行其中任何內容。cron 會以自己的精簡環境啟動命令,因此位於標準系統目錄以外的程式無法找到。/usr/local/bin/opt、語言版本管理工具、Python 虛擬環境或 Go 工作區中的任何內容,都可能發生此問題。工作會在第一行失敗,而 shell 會寫入類似「找不到」的錯誤;實際文字取決於執行該工作的 shell。

找出工作所使用每個命令的實際路徑。

command -v docker
command -v node
readlink -f "$(command -v node)"

接著,將這些絕對路徑直接寫入工作,或在 crontab 頂端設定一次 PATH

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool run

使用 echo "$PATH" 從您自己的機器取得這份清單,並移除只存在於互動式工作階段中的項目。這裡有一項規則很重要:cron 不會展開這些指定行中的變數。PATH=$PATH:/usr/local/bin 會儲存文字 $PATH:/usr/local/bin,因此工作最後取得的搜尋路徑完全不含可用目錄。請將完整清單明確寫出。

版本管理工具不只需要路徑。nvm、pyenv、rbenv 和 asdf 會從您的 .bashrc 安裝 shell 函式或 shims 目錄,而 cron 工作不會讀取該檔案。請以絕對路徑呼叫指定版本的執行檔,或將版本管理工具的 init script 作為您自行撰寫之 script 的第一行。

原因 2:百分號會結束命令

在 crontab 的 command 欄位中,% 不是一般字元。第一個未逸出的 % 會結束命令。其後的所有內容會以標準輸入傳遞給命令,後續每個 % 都會轉換成換行。這是 cron 用來將簡短輸入傳給程式的實際功能,也是日期標記檔名成為 crontab 中經典錯誤項目的原因。

寫入 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site 時,tar 永遠看不到格式化後的日期。cron 會在第一個 % 處截斷該行,因此 shell 會收到未完成的命令替換,而該行的其餘內容則會以標準輸入傳入。請在每個百分號前加上反斜線。

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

這一行會依序由兩個層次讀取。\% 是 cron 規則,由 cron 在啟動任何內容前套用。$(date +\%F)命令替換,由 cron 啟動的 shell 稍後套用。掌握每個字元由哪個層次處理,就是解決問題的關鍵。

較安全的做法是完全不要將邏輯放在 crontab 中。請將邏輯放入 script,這樣百分號就沒有特殊意義。

#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/site

如此一來,crontab 行只需包含路徑與重新導向,不含其他內容。能一眼讀懂的 crontab,才容易進行除錯。

原因 3:你編輯的是哪個 crontab?

crontab 並不只有一份。系統中有多個檔案,擁有者和欄位數量各不相同。工作寫入錯誤的檔案後,就不會生效。

  • crontab -e 會編輯執行該命令之使用者的 crontab。sudo crontab -e 會編輯 root 的 crontab。兩個人排查同一台主機時,經常會讀到不同的檔案。
  • sudo crontab -l -u deploy 會列出其他使用者的 crontab。你可以用它確認應執行該工作的帳號目前實際安裝了哪些工作。
  • /etc/crontab 以及 /etc/cron.d 中的每個檔案,都會在排程與命令之間多出一個欄位,表示執行該工作的使用者。若將五欄格式的使用者 crontab 工作列貼到 /etc/cron.d,命令的第一個字就會被解讀為使用者名稱。
  • /etc/cron.d 中的檔案名稱只能包含字母、數字、底線和連字號。名稱為 backup.shsite.conf 的檔案會因名稱不符合規則而被略過。將它重新命名為 backup,再檢查日誌。
  • /etc/cron.d 中的檔案應由 root 擁有,且群組或其他使用者不得具備寫入權限。ls -l /etc/cron.d 可同時顯示這兩項資訊。
  • 放入 /etc/cron.daily 及其同類目錄的指令碼也必須遵守相同的命名規則,並設定執行權限。缺少執行權限時,工作會遭到靜默略過。
  • /etc/cron.allow/etc/cron.deny 會決定哪些使用者可以安裝 crontab。如果你的主機上存在其中任一檔案,應先讀取該檔案,再假設你的使用者具備安裝權限。

請使用 crontab 命令安裝使用者 crontab,不要手動編輯 spool 檔案,因為 crontab 會在安裝前解析該檔案。儲存後,請閱讀命令回傳的訊息。如果檔案遭拒,先前的版本仍會繼續生效,你的變更也不會套用。這種情況看起來就像 cron 忽略了你的設定。

擁有者也會決定權限。root 的 crontab 中的工作會建立由 root 擁有的檔案,讀取這些檔案的應用程式可能無法寫入。一般使用者 crontab 中的工作則無法讀取僅限 root 存取的目錄。請讓擁有者符合工作需求:應用程式維護工作應由該應用程式自己的帳號執行,這也是 以 system cron job 取代 WordPress wp-cron 背後的原理。工作建立之檔案的模式來自它所繼承的 umask,而該值不一定與 shell 的 umask 相同。因此,如果工作的輸出檔案無法讀取,建議閱讀 umask 如何設定檔案權限

原因 4:輸出送到沒有人讀取的郵件

cron 會收集工作寫入標準輸出和標準錯誤的所有內容。只要工作有任何輸出,cron 就會將文字交給本機郵件系統,收件者是 crontab 的擁有者,或是 MAILTO 所指定的對象。在精簡的 VPS 上,通常未安裝 MTA (mail transfer agent),因此沒有任何元件會傳送這些郵件。錯誤訊息只存在片刻,接著就無處可去。這正是失敗的工作看起來沒有任何訊息的原因。

請改將輸出寫入由你控制的檔案。

0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

>> 會將標準輸出附加至檔案。2>&1 會將標準錯誤導向目前標準輸出的目的地,因此必須放在重新導向之後。若順序相反,寫成 2>&1 >> file,標準錯誤就會保留原本的目的地,而你要追查的錯誤正是唯一不會寫入檔案的部分。

journal 也是適合的目的地。logger 會使用你指定的標籤寫入 syslog。

0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-site

使用 journalctl -t backup-site 讀取內容。這會讓工作本身的輸出與 cron 項目放在一起,方便依時間軸追蹤。如果還需要記錄哪位使用者在主機上執行了哪些命令,那是另一套系統;請參閱稽核伺服器上的使用者命令

在 crontab 頂端加入 MAILTO="",即可關閉其下方工作的郵件通知。將 MAILTO 設為實際地址,只有在系統已具備可運作的 MTA 時才有用。因此,在依賴這項功能前,請先確認郵件確實能離開主機。

除錯時請遵守一項規則:絕對不要附加 > /dev/null 2>&1。這是每個 crontab 中最常見的項目,卻會丟棄你唯一擁有的證據。等工作正常運作後,如果仍需要,再將它加回去。

原因 5:指令碼假設了 cron 不會提供的環境

找到指令並擷取其輸出後,剩下的問題都與工作階段自動提供的環境有關。

  • Shell 不一定是 bash。使用 ls -l /bin/sh 檢查。在 Debian 和 Ubuntu 上,該指令會指向 dash,因此雙中括號測試、陣列和 source 都會因語法錯誤而失敗。為指令碼提供 #!/bin/bash 行並直接呼叫指令碼,或在 crontab 頂端設定 SHELL
  • 工作目錄不是你原本所在的目錄。所有地方都使用絕對路徑,或在指令碼第一行使用 cd 切換至該目錄。相對路徑是工作「手動執行時正常」最常見的單一原因。
  • Locale 不是工作階段使用的設定。任何格式化日期或數字、或排序文字的操作,都可能在不同的 LANG 下產生不同輸出。若後續步驟會剖析該輸出,請在指令碼中設定 locale,不要仰賴預設環境。
  • 沒有 TTY(終端機)。要求確認、開啟編輯器或繪製進度列的指令可能會卡住或結束。加入該工具提供的非互動旗標。
  • 沒有 SSH agent。SSH_AUTH_SOCK 不在 cron 的環境中,因此原本因 agent 已載入而能正常執行的 sshrsync 指令,現在會因驗證失敗而無法執行。為工作指定專用金鑰,並將金鑰擁有者設為執行該工作的使用者。
  • 沒有使用者工作階段匯流排,因此 cron 工作中的 systemctl --user 會在未設定 XDG_RUNTIME_DIR 時失敗。改用 system unit 是較佳做法。

Fedora、Rocky 和 Alma 還有另一個可能原因。SELinux 會限制 cron 工作,因此工作存取標籤異常的路徑時,即使檔案權限正確,仍會遭到拒絕。使用 sudo ausearch -m avc -ts recent 檢查拒絕事件,並先閱讀 伺服器的 SELinux 基礎知識,再停用任何設定。

顯示 cron 環境的 1 分鐘探測

不要猜測 cron 包含哪些環境,直接讀取。撰寫一個傾印所有內容的 script,設定為每分鐘執行一次,等待後再讀取檔案。

cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.sh

在實際執行該工作的使用者之 crontab 中加入一行,兩側都使用絕對路徑。

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

等待 1 分鐘,然後讀取 /home/deploy/cron-probe.log,並與在你自己的 shell 中執行相同指令的結果比較。PATH 行、工作目錄與 locale 通常就能說明失敗原因。請注意設定中的兩個細節:百分比符號位於 script 內,因此不受 cron 規則影響;log 路徑則是該工作使用者可寫入的位置。

找到答案後,立即刪除該 crontab 行。每分鐘執行一次並附加內容到檔案的工作會填滿小容量磁碟,而且會在不易察覺的情況下持續進行。

排程是否符合您的需求?

使用者的 crontab 行開頭包含 5 個欄位:分鐘、小時、每月第幾日、月份、星期幾。其中 2 個欄位的互動方式常讓人誤解。

當每月第幾日與星期幾都受到限制,也就是兩者都不是 * 時,cron 會在任一欄位符合時執行工作。0 0 13 * 5 並不是「13 號星期五」。它會在每月 13 日的午夜,以及每個星期五的午夜執行。若要指定某一天,請將其中一個欄位設為 *,再在指令碼內檢查另一個欄位。

cron 使用系統時區。許多 VPS 映像檔預設設為 UTC(協調世界時),因此您排程在 03:00 執行的工作,會在 03:00 UTC 執行,這可能是您當地下午的時段。timedatectl 會顯示您的伺服器實際使用的時區。請查看實際設定,不要假設它與筆電相同。

另外還有 2 個值得注意的排程陷阱。@reboot 會在 cron 本身啟動時執行,這不代表網路已經就緒。因此,需要 DNS 或遠端主機的工作可能會在開機時失敗,但之後每次手動執行都成功。此外,慢速工作的上一個執行個體尚未結束時,沒有任何機制會阻止新的執行個體再次啟動。請使用鎖定機制包裝它。

*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

當鎖定已被占用時,flock -n 會立即結束。因此,重疊的執行會停止,不會與第一個執行個體同時持續執行。

systemd timer 是更合適的工具時

cron 擅長的事情只有一項:在指定時間執行指令。其他方面都較弱。timer 不需要任何重新導向,就能將輸出寫入 journal;之後也可以查詢退出狀態、針對 network-online.target 設定執行順序,還能加入隨機延遲,避免 100 台伺服器在同一秒同時啟動。工作需要其中任何一項功能時,在 VPS 上設定 systemd service 和 timer,比維護並捍衛一行 crontab 更省事。重試行為也應由此處理,因為 systemd restart policy 會決定失敗後的處置方式,而 cron 完全無法回答這個問題。

小型工作繼續使用 cron。需要相依條件或重試政策的工作,改用 timer。兩者可以在同一台伺服器上執行,因此不必一次完成整個遷移。

FAQ

為什麼 cron 工作手動執行正常,從 cron 執行卻失敗?

因為您的 shell 環境與 cron 的環境不同。登入 shell 會讀取 /etc/profile~/.bashrc,設定 PATH、地區設定及 agent 變數。cron 啟動命令時不會載入這些設定,工作目錄也不同,有時使用的 shell 也不同。請為每個命令使用絕對路徑,在 crontab 頂端或腳本內設定所需內容,並安排每分鐘執行一次的探測工作,將 env | sortpwdid 的結果寫入日誌檔案。如此即可讀取 cron 的實際環境,不必猜測。

如何確認 cron 是否確實執行了工作?

請讀取 daemon 的日誌。在 Debian 和 Ubuntu 上使用 journalctl -u cron;在 Fedora、Rocky 和 Alma 上使用 journalctl -u crond。部分映像檔會透過 rsyslog 將訊息寫入 /var/log 下的檔案。請尋找排程指定分鐘的項目,並確認其中列出您的命令。沒有項目表示 cron 從未取得該排程,因此請確認您編輯的是正確的 crontab。有項目但沒有結果,表示命令已啟動後隨即結束,請透過重新導向擷取其輸出。

為什麼 date +%Y 在 crontab 中會失效?

cron 會將命令欄位中的 % 視為特殊字元。第一個未逸出的 % 會結束命令,後續所有內容會以標準輸入傳給該命令,而之後的每個 % 都會轉換成換行。因此,使用日期格式產生的檔名不會傳給原本要接收它的程式。請將每個百分比符號逸出為 \%,或將命令移至腳本,再從 cron 呼叫該腳本;在腳本內,百分比符號沒有特殊意義。

cron 工作的輸出會送到哪裡?

輸出會送至本機郵件系統,收件者是 crontab 擁有者,或 MAILTO 所指定的對象。大多數 VPS 映像檔都未安裝 mail transfer agent,因此訊息會被丟棄,工作看起來就像沒有輸出。請使用 >> /path/to/log 2>&1 將輸出重新導向至檔案,並維持該順序,讓標準錯誤跟隨標準輸出;或者透過 logger -t myjob 傳送,再使用 journalctl -t myjob 讀取。仍在除錯期間時,請勿使用 > /dev/null 2>&1

應該使用 cron 還是 systemd timer?

固定時間執行簡單命令時,請使用 cron,尤其是您可能需要將工作移至未執行 systemd 的機器時。若需要在不重新導向的情況下將輸出寫入 journal、查詢結束狀態、等待網路啟動後再執行、設定隨機啟動延遲,或在失敗後採用重試政策,請使用 timer。兩者可以在同一台伺服器上並存,因此可在各項工作具備需求時逐一遷移。