你可能在部署腳本、資料庫備份、或是伺服器維護的文件裡看過「cron job」這個詞。
它到底是什麼東西?
為什麼幾乎每台 Linux 主機上都看得到它的身影?
如果你曾經想過「怎麼讓一支腳本每天凌晨自動跑一次」,那你要找的答案很可能就是 cron。
我們先從最基本的問題開始:cron 到底在解決什麼問題?
Cron 是什麼?
先講一下這個名字是怎麼來的,你就不會只把它當成一個沒有意義的代號。
cron 這個字源自希臘文「chronos」,意思是「時間」,因為它的工作本質,就是要在正確的時間點把事情觸發出來。
舉個例子,假設你希望「每天凌晨兩點自動備份資料庫」,重點不是「執行一次備份」這個動作本身,而是「凌晨兩點」這個時間點——動作是固定的,真正的挑戰在於準時觸發,一分鐘都不能提早或延後太多。
問題是,要做到「隨時掌握時間、時間一到就觸發」,這支程式就不能像一般程式那樣執行完就結束,而是得一直留在背景待命。
在 Unix 的世界裡,這種一直在背景默默執行、不需要你手動操作、系統開機就自動啟動的程式,有個專門的稱呼,叫做 daemon。
cron 就是這樣一支 daemon:因為工作跟時間綁在一起,所以才需要隨時待命,也因此用「時間」的希臘文來命名。
知道這個由來之後,它的功能就很直覺了:你告訴 cron「什麼時間點」該做什麼事,它就負責在對的時間點把事情觸發出來。
具體來說,Cron 是 Linux/Unix 系統裡的一個排程服務(scheduler),做法是每分鐘醒來檢查一次「現在有沒有工作要做」。
如果有,它就把對應的指令跑起來;如果沒有,它就繼續睡回去,一分鐘後再檢查一次。
負責這件事的程式正式名稱就是 cron daemon(通常寫作 crond),也就是前面提到的那支背景程式。
而 cron job,指的就是交給 cron daemon 排程執行的那個工作——可能是一支腳本、一個指令,或是一支程式。
這段小結一下:cron 是排程服務本身,cron daemon 是實際跑在背景的程式,cron job 則是你交給它去執行的任務。
三者的關係搞清楚後,接下來的內容會好懂很多。
值得一提的是,cron daemon 這支背景程式並不是寫死在系統核心裡的東西,而是一個獨立的套件(常見的實作叫 cronie 或 vixie-cron),技術上可以移除、也可以重裝。
不過幾乎所有主流的 Linux 發行版(例如 Ubuntu、Debian、CentOS、Fedora)在預設安裝時就會把它裝好,所以你開一台新的 Linux 主機,通常直接就能用 crontab -e,不用額外裝東西。
Crontab:cron 讀取工作清單的地方
cron daemon 每分鐘檢查一次「有沒有工作要做」,但它是怎麼知道要做什麼、什麼時候做的?
答案是它會去讀一份叫做 crontab 的設定檔。
crontab 全名是 cron table,你可以把它想成是一張時刻表:每一行代表一個 cron job,記錄著「這個工作應該在什麼時間點被觸發」以及「觸發時要執行哪個指令」。
crontab 裡的每一行,格式長這樣:
分 時 日 月 星期 要執行的指令實際範例:
30 2 * * * /home/user/backup.sh我們一段一段拆開看:
30:分鐘欄位,代表第 30 分2:小時欄位,代表凌晨 2 點*:日期欄位,*代表「每一天都符合」*:月份欄位,同樣代表「每個月都符合」*:星期欄位,代表「星期幾都符合」/home/user/backup.sh:真正要執行的指令
合起來的意思就是:每天凌晨 2 點 30 分,執行一次 backup.sh。
* 這個符號的白話理解是「這個欄位不設限,全部都算」。
如果你只想在特定日期或星期執行,就把 * 換成具體數字。
例如 30 2 * * 1 代表「每週一凌晨 2 點 30 分執行」,因為星期欄位的 1 代表星期一(星期天是 0)。
除了具體數字和 *,crontab 語法還支援幾種常用寫法:
0 */2 * * * → 每 2 小時的整點執行一次(*/2 代表每隔 2 單位)
0 9-18 * * * → 每天早上 9 點到晚上 6 點,每個整點都執行(9-18 是範圍)
0 0 1,15 * * → 每個月的 1 號和 15 號執行(逗號代表列舉多個值)這段小結:crontab 的五個時間欄位分別對應「分、時、日、月、星期」,搭配 *(不設限)、/(間隔)、-(範圍)、,(列舉)這幾種符號,就能組合出幾乎所有你想要的排程規則。
如何新增、查看、刪除 crontab
了解語法之後,下一個問題是:這份 crontab 檔案要怎麼編輯?
每個使用者都有屬於自己的一份 crontab,cron 提供了專用指令來管理它,而不是叫你直接去改某個檔案。
編輯自己的 crontab:
crontab -e這個指令會用預設的文字編輯器(通常是 vim 或 nano)打開你的 crontab,你可以在裡面新增、修改、刪除任務。
存檔離開後,cron daemon 就會讀到新的排程規則,不需要重啟系統。
查看目前的 crontab 內容:
crontab -l刪除整份 crontab(清空所有排程):
crontab -r這裡有個新手常踩的雷,我們用對比的方式來看:
❌ 有問題的寫法:
30 2 * * * backup.sh這樣寫在大多數情況下會失敗,因為 cron 執行指令時並不是在你熟悉的終端機環境下跑,它不一定知道 backup.sh 這個相對路徑是相對於哪裡。
✅ 改善後的寫法:
30 2 * * * /home/user/scripts/backup.sh一律使用絕對路徑,明確告訴 cron 這個檔案完整的位置在哪裡,才能確保排程穩定執行,不會因為工作目錄不同而找不到檔案。
常見陷阱:環境變數、時區與 log
搞懂語法和基本操作之後,實際上線時最容易踩坑的,反而是三個看似跟「排程」無關的細節。
陷阱一:環境變數不見了
你在終端機手動執行一支腳本時,通常會繼承你登入帳號的完整環境變數(像是 PATH、NODE_ENV 這些)。
但 cron daemon 啟動 job 時,用的是一份非常精簡的環境變數,通常只有基本的 PATH,不會載入你 .bashrc 或 .zshrc 裡設定的內容。
這就是為什麼一支腳本你手動執行沒問題,放進 cron 卻找不到指令(例如找不到 node 或 python)。
解法有兩種,你可以擇一使用,或是兩種一起用。
解法一:指令裡明確指定完整路徑
先用 which 指令查出這個指令實際裝在哪裡:
which node假設輸出是 /usr/bin/node,代表這就是 node 這個指令的絕對路徑。
接下來在 crontab 裡,把原本的 node 換成完整路徑:
30 2 * * * /usr/bin/node /home/user/scripts/backup.js而不是只寫 node backup.js,靠 cron 幫你去猜 node 在哪裡。
解法二:在腳本開頭自己載入需要的環境變數
如果你的腳本裡用到很多指令,一個一個換成絕對路徑會很麻煩,這時候可以改成在腳本最開頭手動補上需要的環境變數,例如:
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin:$PATH
export NODE_ENV=production
node backup.js這樣不管 cron 給的 PATH 有多精簡,腳本執行到 node backup.js 這一行時,環境變數已經被你手動補齊了,也能正常找到指令。
陷阱二:時區設定跟你想的不一樣
cron daemon 使用的是系統設定的時區,不是你電腦或瀏覽器所在地的時區。
如果你的伺服器設定的是 UTC,而你希望「台北時間凌晨 2 點」執行,那你在 crontab 裡寫的小時欄位,要換算成 UTC 時間,而不是直接寫台北時間的 2。
台北時間是 UTC+8,也就是台北時間比 UTC 快 8 小時,換算時要把台北時間往回減 8 小時。
我們用一個實際例子算一次:你希望「台北時間凌晨 2 點」執行,往回減 8 小時,就是「UTC 時間前一天的 18 點」。
所以 crontab 應該寫成:
0 18 * * * /home/user/scripts/backup.sh小時欄位寫 18,而不是你直覺想寫的 2,因為 cron daemon 是照系統的 UTC 時間在比對,不是照台北時間。
可以用以下指令確認系統目前的時區:
timedatectl執行後如果看到 Time zone: Etc/UTC,就代表系統用的是 UTC,需要自己換算。
如果看到的是 Time zone: Asia/Taipei,代表系統本身就設定成台北時區,這時候直接寫台北時間的小時數就可以了,不用再換算。
如果你不想每次都手動換算,另外一個做法是在 crontab 檔案最上方指定時區,讓後面的排程都用指定的時區來計算:
CRON_TZ=Asia/Taipei
0 2 * * * /home/user/scripts/backup.sh這樣寫的話,0 2 就會直接照台北時間凌晨 2 點觸發,不用再自己換算成 UTC。
不過要注意,CRON_TZ 這個寫法不是每一種 cron 實作都支援,用之前建議先確認你系統上的 cron 版本有沒有支援這個語法。
陷阱三:看不到執行結果
cron job 是在背景默默執行的,預設情況下,如果指令有任何輸出(不管是正常的 print,還是錯誤訊息),都不會顯示在你的畫面上,你完全不知道它到底跑成功還是失敗。
這段小結一下解法:主動把輸出導向一個 log 檔案,才能事後回頭檢查:
30 2 * * * /home/user/scripts/backup.sh >> /home/user/logs/backup.log 2>&1要看懂這行指令,得先知道 Linux 裡每個程式執行時都有兩條輸出管道:一條叫標準輸出(stdout,代號是 1),專門輸出正常訊息;另一條叫標準錯誤輸出(stderr,代號是 2),專門輸出錯誤訊息。
平常你在終端機執行指令時,這兩條管道預設都是輸出到畫面上,所以你才看得到印出來的文字和錯誤訊息混在一起。
>> 這個符號,代表把標準輸出(也就是代號 1 的那條管道)附加寫入後面接的檔案,這裡是寫進 /home/user/logs/backup.log。
用 >>(兩個角括號)而不是 >(一個角括號),是因為 > 每次執行都會清空檔案重寫,>> 才會保留舊紀錄、把新的內容加在後面,不然每次執行完前一次的紀錄就會被洗掉。
2>&1 這段稍微繞一點:2 代表標準錯誤輸出,&1 代表「跟標準輸出導去同一個地方」,合起來的意思就是「把標準錯誤輸出也導到標準輸出現在導去的地方」。
因為前面 >> 已經把標準輸出導去 backup.log 了,所以 2>&1 接在後面,效果就是錯誤訊息也一起寫進同一個 log 檔案。
這裡有個順序上的細節要注意:2>&1 一定要寫在 >> backup.log 後面,如果順序寫反了,錯誤訊息會被導到「畫面」而不是「檔案」,因為 2>&1 執行的當下,標準輸出還沒被導向檔案。
這樣不管腳本執行成功或失敗,結果都會留下紀錄,方便你之後追蹤問題。
另外提醒一點,log 檔案會隨著 cron job 一直執行而越長越大,長期下來可能會佔用不少硬碟空間,實務上通常會搭配 logrotate 這類工具,定期把舊的 log 壓縮或刪除,這部分可以之後再深入研究,這裡先知道有這個問題存在就好。
實務應用:什麼時候該用 cron?
Cron 最適合處理「有固定時間規律、不需要即時反應」的重複性工作,常見場景包括:
- 資料庫備份:每天凌晨自動備份一次,避免手動忘記
- 清理暫存檔:每週清掉一次過期的 log 或快取檔案
- 定期發送報表:每個月 1 號自動寄出上個月的營運數據
- 健康檢查:每 5 分鐘檢查一次服務是否還活著,異常就發通知
但 cron 也有它的侷限。
如果你需要的是「事件觸發」(例如有人上傳檔案就馬上處理),或是需要更精細的重試機制、任務相依關係管理,cron 就不太夠用了,這種情況通常會考慮 message queue 或是更完整的排程系統(例如 Airflow)。
另外要注意的是,如果一台機器上有大量 cron job,而且彼此之間有時間先後的依賴關係,單純用 crontab 排時間會很難維護,這時候也是該考慮更專業工具的訊號。
重點整理
- Cron 是 Linux/Unix 上的排程服務,cron daemon 是實際運作的背景程式,cron job 是被排程執行的任務
- cron daemon 是獨立套件,不是系統核心內建,但主流 Linux 發行版預設都會裝好
- crontab 是 cron 讀取排程規則的設定檔,格式是「分 時 日 月 星期 指令」五個欄位加上要執行的內容
*代表不設限,/代表間隔,-代表範圍,,代表列舉多個值- 用
crontab -e編輯、crontab -l查看、crontab -r刪除自己的排程 - 指令一律用絕對路徑,避免 cron 找不到檔案
- cron 執行環境的環境變數很精簡,可以用
which查出絕對路徑,或在腳本開頭手動補上環境變數 - cron 用的是系統時區,跟台北時間換算時要注意時差,也可以用
CRON_TZ直接指定時區 - 用
>> log 檔案 2>&1把標準輸出和標準錯誤輸出都導向同一個 log 檔案,順序不能寫反 - cron 適合固定時間、重複性高的背景工作,不適合需要即時觸發或複雜依賴關係的任務