伺服器維運

crontab 命令詳解:定時任務格式、引數與配置範例

crontab 是 Linux 定時任務的配置表,每行寫“分 時 日 月 周”五個時間欄位加一條命令,cron 服務每分鐘檢查一次、到點執行。crontab -e 編輯,-l 檢視,-r 刪除全部任務且不會確認;系統級任務寫在 /etc/crontab 或 /etc/cron.d/,每行比使用者 crontab 多一個使用者名稱欄位。

作者 頂聯產品團隊發布於 6 分鐘閱讀

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 怎麼編輯

  1. 執行 crontab -e。Ubuntu 第一次執行時會讓你選擇編輯器,選 nano 最簡單;想換成 Vim,執行 select-editor 重新選,或者臨時用 EDITOR=vim crontab -e。
  2. 在檔案末尾新起一行寫任務,例如 */5 * * * * /usr/local/bin/check.sh >> /var/log/check.log 2>&1。
  3. 儲存退出。nano 按 Ctrl+O、回車儲存,Ctrl+X 退出;Vim 輸入 :wq。看到 crontab: installing new crontab 就說明已經生效,不需要重啟 cron 服務。
  4. 格式寫錯時會提示 errors in crontab file, can't install,並詢問是否重新編輯,回答 y 回去修改。
  5. 執行 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 更合適。兩者可以並存,但同一個任務只在一處配置,避免重複執行。

想看看在你的機房裡怎麼用?

從一次產品示範開始,梳理你的業務鏈路。

查看價格
服務熱線 400-112-2951

讓我們聊聊你的 IDC 業務

掃碼新增企業微信,預約產品示範或申請試用。

Toplink 企業微信二維碼

長按儲存或使用企業微信 / 微信掃描

也可致電
400-112-2951
Telegram
@TopLink88