crontab 命令詳解:定時任務格式、引數與配置範例
crontab 是 Linux 定時任務的配置表,每行寫“分 時 日 月 周”五個時間欄位加一條命令,cron 服務每分鐘檢查一次、到點執行。crontab -e 編輯,-l 檢視,-r 刪除全部任務且不會確認;系統級任務寫在 /etc/crontab 或 /etc/cron.d/,每行比使用者 crontab 多一個使用者名稱欄位。
crontab 是 Linux 上配置定時任務的命令,也是這張任務表本身的名字。每個使用者有一張自己的 crontab,每行一個任務,格式是“分 時 日 月 周 命令”,例如 30 2 * * * /usr/local/bin/backup.sh 表示每天 2:30 執行備份指令碼。cron 服務(RHEL 系叫 crond,Debian 系叫 cron)每分鐘檢查一次所有任務表,時間對上就執行。日常操作只需要四個引數:crontab -e 編輯,crontab -l 檢視,crontab -r 刪除,crontab -u 使用者名稱 指定使用者。寫定時任務最容易出問題的往往不是時間格式,而是執行環境:cron 裡的 PATH 很短、不讀 .bashrc、% 有特殊含義,後文逐一說明。
crontab 引數說明:-e、-l、-r、-u
| 命令 | 作用 | 說明 |
|---|---|---|
crontab -e |
編輯當前使用者的定時任務 | 用 VISUAL 或 EDITOR 環境變數指定的編輯器開啟,儲存退出後自動檢查語法並安裝 |
crontab -l |
檢視當前使用者的定時任務 | 沒有任務時提示 no crontab for 使用者名稱 |
crontab -r |
刪除當前使用者的全部定時任務 | 不做確認,直接清空 |
crontab -ri |
刪除前先詢問 | cronie 和 Debian 的 cron 都支援 -i |
crontab -u 使用者 -l、crontab -u 使用者 -e |
檢視、編輯指定使用者的任務 | 只有 root 能用 |
crontab 檔名 |
用檔案內容替換當前使用者的任務表 | 原有任務被整體覆蓋 |
crontab -e 怎麼編輯
- 執行
crontab -e。Ubuntu 第一次執行時會讓你選擇編輯器,選 nano 最簡單;想換成 Vim,執行select-editor重新選,或者臨時用EDITOR=vim crontab -e。 - 在檔案末尾新起一行寫任務,例如
*/5 * * * * /usr/local/bin/check.sh >> /var/log/check.log 2>&1。 - 儲存退出。nano 按 Ctrl+O、回車儲存,Ctrl+X 退出;Vim 輸入
:wq。看到crontab: installing new crontab就說明已經生效,不需要重啟 cron 服務。 - 格式寫錯時會提示
errors in crontab file, can't install,並詢問是否重新編輯,回答 y 回去修改。 - 執行
crontab -l,確認任務已經在表裡。
幾條使用習慣:
- 改之前先備份。
-e和-r在鍵盤上挨著,敲錯一個字母就清空了全部任務:crontab -l > ~/crontab.bak.$(date +%F)。 - 指令碼里追加任務不用開啟編輯器:
(crontab -l 2>/dev/null; echo '0 4 * * * /usr/local/bin/cleanup.sh') | crontab -。 - 不要直接改存放檔案。 使用者的任務表實際儲存在
/var/spool/cron/使用者名稱(RHEL 系)或/var/spool/cron/crontabs/使用者名稱(Debian 系),要透過 crontab 命令修改,命令會檢查語法,cron 也會在下一分鐘識別到變化。 - 誰能用 crontab,由
/etc/cron.allow和/etc/cron.deny控制:存在 cron.allow 時,只有其中列出的使用者可以使用。
五個時間欄位怎麼寫
┌───────────── 分鐘(0–59)
│ ┌─────────── 小時(0–23)
│ │ ┌───────── 日(1–31)
│ │ │ ┌─────── 月(1–12,也可寫 jan–dec)
│ │ │ │ ┌───── 星期(0–7,0 和 7 都表示週日,也可寫 sun–sat)
│ │ │ │ │
* * * * * 要執行的命令
四個特殊符號:
| 符號 | 含義 | 例子 |
|---|---|---|
* |
該欄位的任意值 | 小時寫 * 表示每個小時 |
, |
列舉多個值 | 日寫 1,15 表示 1 號和 15 號 |
- |
範圍 | 小時寫 9-18 表示 9 點到 18 點 |
/ |
步長 | 分鐘寫 */10 表示每 10 分鐘;0-30/10 表示 0、10、20、30 分 |
常用寫法速查:
| 表示式 | 含義 |
|---|---|
*/5 * * * * |
每 5 分鐘 |
0 * * * * |
每小時整點 |
30 2 * * * |
每天 02:30 |
0 9-18 * * 1-5 |
週一到週五,9 點到 18 點的每個整點 |
0 3 * * 0 |
每週日 03:00 |
0 0 1 * * |
每月 1 日 00:00 |
0 4 1,15 * * |
每月 1 日和 15 日的 04:00 |
*/10 0-6 * * * |
0 點到 6 點之間每 10 分鐘 |
@reboot |
cron 服務啟動時執行一次,相當於開機執行 |
@hourly、@daily、@weekly、@monthly |
分別等於 0 * * * *、0 0 * * *、0 0 * * 0、0 0 1 * * |
三個容易寫錯的地方:
- 日和星期同時指定時是“或”的關係。
0 0 13 * 5不是“13 號並且是週五”,而是每月 13 號和每個週五都會執行。兩個欄位中有一個是*時,才只按另一個欄位判斷。 - 步長不會跨週期延續。 日寫
*/7是每月的 1、8、15、22、29 號,到下個月又從 1 號開始,並不是嚴格的每 7 天一次。 - 沒有“每月最後一天”的寫法。 可以讓 28 到 31 號都觸發,再在命令裡判斷明天是不是 1 號:
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh。
/etc/crontab、/etc/cron.d 與使用者 crontab 的區別
| 位置 | 怎麼修改 | 每行格式 | 以誰的身份執行 | 適合放什麼 |
|---|---|---|---|---|
| 使用者 crontab | crontab -e |
5 個時間欄位 + 命令 | 任務表的所有者 | 某個使用者或某個應用自己的任務 |
/etc/crontab |
root 直接編輯檔案 | 5 個時間欄位 + 使用者名稱 + 命令 | 行內指定的使用者 | 系統自帶的排程,一般不動 |
/etc/cron.d/ 下的檔案 |
root 新建檔案 | 同 /etc/crontab | 行內指定的使用者 | 自己的系統級任務,軟體包、自動化部署寫入的任務 |
/etc/cron.hourly、cron.daily、cron.weekly、cron.monthly |
放入可執行指令碼 | 不是 crontab 格式,就是指令碼本身 | root | 不在乎具體幾點執行的日常維護 |
幾個要點:
- 使用者名稱欄位是最常見的錯誤來源。 把
crontab -e裡的寫法原樣貼進 /etc/crontab 或 /etc/cron.d,命令的第一個詞會被當成使用者名稱;反過來,把帶使用者名稱的行貼進 crontab -e,使用者名稱會被當成命令去執行。 - 自己的系統級任務放 /etc/cron.d,不要改 /etc/crontab。 /etc/crontab 屬於系統軟體包的配置檔案,升級時可能被提示覆蓋;每個任務一個獨立檔案,也方便用配置管理工具下發和刪除。
- Debian 系對檔名有要求。 /etc/cron.d 下的檔名,以及 cron.daily 等目錄裡的指令碼名,只能由字母、數字、下劃線和連字元組成,帶點的會被忽略。最常見的坑是把
cleanup.sh放進 /etc/cron.daily,它永遠不會執行;用run-parts --test /etc/cron.daily可以列出會被執行的指令碼。/etc/cron.d 下的檔案還要屬於 root,且不能被其他使用者寫入。 - cron.daily 的執行時間不固定。 Debian 系由 /etc/crontab 在每天早上排程,裝了 anacron 時交給 anacron;RHEL 系由 anacron 按 /etc/anacrontab 執行,預設只在 3 點到 22 點之間執行並加隨機延遲,通常在凌晨 3 點多。需要精確時間的任務,寫成 /etc/cron.d 裡的具體時間。
一個 /etc/cron.d 檔案的例子:
# /etc/cron.d/app-maint (檔名不要帶點)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# 分 時 日 月 周 使用者 命令
*/10 * * * * www-data /usr/local/bin/queue-check.sh >/dev/null 2>&1
15 3 * * * root /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
寫完執行 chmod 644 /etc/cron.d/app-maint,cron 會自動讀取,不用重啟服務。檔案的最後一行要以換行結束,否則最後一個任務可能不會被識別。
維運常用的定時任務配置範例
日誌切割
系統自帶的 logrotate 每天會執行一次(由 cron.daily 或 systemd 的 logrotate.timer 觸發),自己的應用日誌建議在 /etc/logrotate.d/ 下寫一份配置交給它。沒有 logrotate 配置、又想按日期儲存的 Nginx 存取日誌,可以用 crontab 直接切割:
# 每天 00:00 把存取日誌按昨天的日期改名,再讓 Nginx 重新開啟日誌檔案
0 0 * * * mv /var/log/nginx/access.log /var/log/nginx/access-$(date -d yesterday +\%F).log && kill -USR1 $(cat /run/nginx.pid)
USR1 訊號讓 Nginx 重新開啟日誌檔案,不中斷服務;pid 檔案路徑以 nginx.conf 裡的 pid 配置為準。系統已經用 logrotate 管理 Nginx 日誌時,不要再加這一條,兩邊會重複切割。
資料庫備份
複雜的邏輯寫進指令碼,crontab 裡只留一行呼叫,指令碼里的 % 也就不用轉義了:
#!/bin/bash
# /usr/local/bin/mysql-backup.sh
set -euo pipefail
DIR=/data/backup/mysql
mkdir -p "$DIR"
OUT="$DIR/all-$(date +%F).sql.gz"
mysqldump --defaults-extra-file=/root/.backup.cnf --single-transaction --routines --all-databases \
| gzip > "$OUT.tmp"
mv "$OUT.tmp" "$OUT"
find "$DIR" -name 'all-*.sql.gz' -mtime +7 -delete
# crontab -e 中加入:每天 02:30 執行,輸出追加到日誌
30 2 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
set -o pipefail 讓 mysqldump 失敗時整條管道返回失敗,配合 set -e 指令碼立即退出:殘缺的結果只留在 .tmp 檔案裡,不會被改名成正式備份,後面清理舊備份的 find 也不會執行;資料庫帳號寫在許可權為 600 的 /root/.backup.cnf 裡,不要寫進指令碼或 crontab。find -mtime +7 按整天取整計算,刪除的是至少 8 天前修改的檔案。
清理臨時檔案、防止任務重疊、磁碟檢查
# 每天 04:00 刪除 /tmp/app 下超過 3 天未修改的檔案
0 4 * * * find /tmp/app -type f -mtime +3 -delete
# 每 5 分鐘同步一次;上一輪還沒跑完時,這一輪直接跳過
*/5 * * * * flock -n /run/lock/sync.lock /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
# 每 30 分鐘檢查根分割槽,使用率超過 85% 時寫一條系統日誌,便於監控採集
*/30 * * * * df -P / | awk 'NR==2 && $5+0 > 85 {print "root usage " $5}' | logger -t disk-check
flock 這一條很實用:同步、採集這類任務偶爾會因為網路慢而超時,如果不加鎖,下一輪照常啟動,幾輪疊加下來會把機器拖垮。
crontab 不執行怎麼排查
| 現象 | 原因 | 解決 |
|---|---|---|
手動執行正常,cron 裡報 command not found |
cron 的 PATH 只有 /usr/bin:/bin 等少數目錄,也不讀 .bashrc、.bash_profile |
命令寫絕對路徑,或在任務表頂部寫 PATH=... |
| 命令被截斷、行為異常 | % 在 crontab 裡會被換成換行,後面的內容變成命令的標準輸入 |
寫成 \%,或者把命令放進指令碼 |
| 任務好像沒執行,也看不到任何輸出 | 輸出預設以郵件發給使用者,本機沒有郵件服務時就丟了 | 命令末尾重定向 >> 日誌檔案 2>&1 |
指令碼報 /bin/bash^M: bad interpreter |
指令碼在 Windows 上編輯過,帶 CRLF 換行 | sed -i 's/\r$//' 指令碼名,並確認有執行許可權 |
| /etc/cron.d 裡的任務不執行 | 缺使用者名稱欄位、檔名帶點(Debian 系)、許可權不對、最後一行沒有換行 | 按上一節的要求逐項檢查 |
| 執行時間差了 8 小時 | 改時區後沒有重啟 cron 服務,仍按舊時區 | 重啟 cron 服務 |
排查用到的命令:
systemctl status cron # Debian、Ubuntu;RHEL 系把 cron 換成 crond
grep CRON /var/log/syslog # Debian、Ubuntu 的執行記錄;沒有 syslog 檔案時用 journalctl -u cron
tail -f /var/log/cron # RHEL 系的執行記錄
# 用接近 cron 的精簡環境手動跑一遍,能復現大部分環境變數問題
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=/root LOGNAME=root /bin/sh -c '/usr/local/bin/mysql-backup.sh'
日誌裡出現 CMD (/usr/local/bin/mysql-backup.sh),說明 cron 已經按時啟動了命令,問題在命令本身,去看它自己的輸出日誌;連 CMD 記錄都沒有,說明時間寫錯了、任務表沒裝上,或者 cron 服務沒在執行。
在管理系統裡怎麼落地
crontab 平時很少有人去看,入侵者卻常在裡面寫一條定時下載指令碼的任務來維持控制;人員交接時,也常常沒人說得清某條任務是誰加的、做什麼用。所以修改定時任務這類操作,要放在留痕的通道里完成。Toplink DCIM 的 IPMI 遠端管理提供瀏覽器裡的 SSH 控制檯,每次會話自動錄影,SSH 會話還記錄命令級日誌,誰在什麼時候執行了 crontab -e、改了什麼,事後可以回放核對。定時任務把伺服器拖垮、SSH 登不上去時,同一頁面的 VNC 控制檯走 BMC 帶外通道,可以登入本地終端把出問題的任務註釋掉。
常見問題
crontab 能精確到秒嗎?
不能,最小單位是分鐘。要每 30 秒執行一次,可以寫兩行:* * * * * /usr/local/bin/job.sh 和 * * * * * sleep 30; /usr/local/bin/job.sh。更頻繁、或者對時間精度有要求的任務,改用 systemd timer(OnUnitActiveSec 配合 AccuracySec)或常駐程序。
crontab -r 誤刪了全部任務,還能恢復嗎?
crontab 沒有回收站,只能從備份恢復。沒有備份時可以從 cron 日誌裡找線索:Debian 系用 grep CRON /var/log/syslog 或 journalctl -u cron,RHEL 系看 /var/log/cron,CMD 記錄列出了最近執行過的命令和使用者,再按執行時間反推時間欄位。以後修改前先用 crontab -l 備份一份。
伺服器改了時區,crontab 按哪個時間執行?
cron 按 cron 服務程序的時區判斷時間。用 timedatectl set-timezone Asia/Shanghai 改完時區後,重啟 cron 服務(RHEL 系是 crond,Debian 系是 cron),新時區才會生效。RHEL 系的 cronie 還支援在任務表裡寫 CRON_TZ= 為這張表單獨指定時區。
crontab 和 systemd timer 怎麼選?
簡單的週期任務用 crontab 足夠。需要依賴其他服務、限制 CPU 和記憶體、錯過執行時間後開機補跑(Persistent=true)、日誌統一進 journal 時,用 systemd timer 更合適。兩者可以並存,但同一個任務只在一處配置,避免重複執行。