Crontab: ví dụ lập lịch tác vụ trên Linux, cú pháp và lỗi thường gặp
Crontab là bảng lịch mà daemon cron dựa vào để chạy lệnh: mỗi dòng gồm năm trường thời gian (phút, giờ, ngày trong tháng, tháng, thứ trong tuần) và lệnh, ví dụ */5 * * * * /opt/backup.sh chạy script mỗi 5 phút. Bảng được sửa bằng crontab -e; phần lớn sự cố đến từ môi trường tối giản của cron, nên hãy dùng đường dẫn đầy đủ và chuyển hướng đầu ra vào nhật ký.
Cron là bộ lập lịch tác vụ tiêu chuẩn trên Linux: daemon cron mỗi phút đọc các bảng lịch (crontab) và chạy những lệnh đến giờ. Nhờ cron mà sao lưu ban đêm, dọn file tạm, gia hạn chứng chỉ và gửi báo cáo được thực hiện tự động. Dưới đây là cú pháp, các ví dụ crontab cho những trường hợp phổ biến, và cách xử lý các lỗi khiến tác vụ "lặng lẽ" không chạy.
Cách mở và sửa crontab
Mỗi người dùng có bảng riêng. Làm việc với bảng qua lệnh crontab, không sửa trực tiếp các file trong /var/spool/cron:
| Lệnh | Chức năng |
|---|---|
crontab -e | mở bảng của mình trong trình soạn thảo (lần đầu sẽ hỏi chọn trình soạn thảo) |
crontab -l | hiển thị bảng hiện tại |
crontab -r | xóa toàn bộ bảng, không hỏi xác nhận |
crontab -i -r | xóa có xác nhận (Debian/Ubuntu, RHEL) |
sudo crontab -u alice -e | sửa bảng của người dùng khác |
crontab tệp | thay bảng bằng nội dung của tệp |
Sau khi lưu, crontab -e kiểm tra cú pháp và báo crontab: installing new crontab. Nếu có lỗi trong một dòng, bạn sẽ thấy errors in crontab file, can't install và được mời sửa lại. Không cần khởi động lại daemon — thay đổi được áp dụng tự động.
Hãy cẩn thận với -r: phím e và r nằm gần nhau, còn bảng không có bản sao lưu. Thói quen nên có là giữ bản dự phòng:
crontab -l > ~/crontab.bak
# khôi phục
crontab ~/crontab.bak
Cú pháp crontab: năm trường thời gian
Mỗi dòng tác vụ có dạng:
# ┌───────── phút (0–59)
# │ ┌─────── giờ (0–23)
# │ │ ┌───── ngày trong tháng (1–31)
# │ │ │ ┌─── tháng (1–12 hoặc jan–dec)
# │ │ │ │ ┌─ thứ trong tuần (0–7 hoặc sun–sat; 0 và 7 — Chủ nhật)
# │ │ │ │ │
* * * * * lệnh
Trong mỗi trường có thể dùng các ký tự đặc biệt:
| Ký tự | Ý nghĩa | Ví dụ |
|---|---|---|
* | mọi giá trị | * * * * * — mỗi phút |
, | danh sách | 0 9,18 * * * — lúc 9:00 và 18:00 |
- | khoảng | 0 9-18 * * * — mỗi giờ từ 9 đến 18 |
/ | bước nhảy | */15 * * * * — mỗi 15 phút |
Bước nhảy có thể kết hợp với khoảng: 0 8-20/2 * * * — mỗi hai giờ từ 8 đến 20 (8, 10, …, 20). Thời gian tính theo múi giờ của hệ thống — kiểm tra bằng timedatectl.
Ví dụ crontab cho từng lịch
| Lịch | Dòng crontab |
|---|---|
| mỗi phút | * * * * * |
| mỗi 5 phút | */5 * * * * |
| mỗi 30 phút | */30 * * * * hoặc 0,30 * * * * |
| mỗi giờ, đúng đầu giờ | 0 * * * * |
| mỗi 6 giờ | 0 */6 * * * |
| mỗi ngày lúc 3:00 | 0 3 * * * |
| mỗi ngày lúc 23:45 | 45 23 * * * |
| ngày thường lúc 9:00 | 0 9 * * 1-5 |
| cuối tuần lúc 12:00 | 0 12 * * 6,0 |
| thứ Hai lúc 8:30 | 30 8 * * 1 |
| ngày 1 hằng tháng lúc 00:00 | 0 0 1 * * |
| mỗi quý | 0 0 1 1,4,7,10 * |
| mỗi năm, ngày 1 tháng 1 | 0 0 1 1 * |
| mỗi 10 phút trong giờ làm việc | */10 9-18 * * 1-5 |
Dòng tác vụ đầy đủ gồm lịch cộng với lệnh:
# sao lưu cơ sở dữ liệu mỗi đêm lúc 3:00
0 3 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
# dọn file tạm cũ hơn 7 ngày vào Chủ nhật
0 4 * * 0 /usr/bin/find /tmp/app -type f -mtime +7 -delete
# kiểm tra trang web mỗi 5 phút
*/5 * * * * /usr/bin/curl -fsS https://example.com/health > /dev/null || /usr/local/bin/alert.sh
Các chuỗi đặc biệt @reboot, @daily và những chuỗi khác
Thay cho năm trường, có thể viết một bí danh ngắn:
| Bí danh | Tương đương |
|---|---|
@reboot | một lần khi daemon cron khởi động (sau khi hệ thống boot) |
@hourly | 0 * * * * |
@daily / @midnight | 0 0 * * * |
@weekly | 0 0 * * 0 |
@monthly | 0 0 1 * * |
@yearly / @annually | 0 0 1 1 * |
@reboot /home/alice/bin/start-tunnel.sh >> /home/alice/tunnel.log 2>&1
Với dịch vụ phải chạy liên tục và tự khởi động lại khi bị lỗi, @reboot là giải pháp yếu: nên viết unit systemd với Restart=on-failure.
Cách chạy tác vụ mỗi N giây
Đơn vị nhỏ nhất của cron là một phút. Nếu cần chạy thường xuyên hơn, có hai cách. Cách thứ nhất là thêm nhiều dòng với độ trễ bằng sleep:
* * * * * /opt/check.sh
* * * * * sleep 30; /opt/check.sh
Cách thứ hai, đúng đắn hơn, là timer của systemd với OnUnitActiveSec=30s. Nếu tác vụ phải chạy nhiều hơn một lần mỗi phút, thường đó là việc của daemon hoặc timer chứ không phải cron.
Crontab hệ thống: /etc/crontab và /etc/cron.d
Ngoài bảng của người dùng còn có các bảng hệ thống. Định dạng của chúng khác: giữa thời gian và lệnh có thêm tên người dùng sẽ chạy lệnh đó.
# /etc/cron.d/certbot-renew
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 */12 * * * root certbot renew --quiet
Vị trí các file:
/etc/crontab— bảng hệ thống chính; trên Debian/Ubuntu, từ đây chạy các thư mục bên dưới./etc/cron.d/— các file tác vụ riêng biệt, tiện để gói phần mềm hoặc Ansible đặt vào./etc/cron.hourly/,cron.daily/,cron.weekly/,cron.monthly/— đặt các script thực thi được mà không cần lịch; chúng được chạy bởirun-parts(hoặc anacron).
Cái bẫy của run-parts: trên Debian và Ubuntu nó bỏ qua các file có dấu chấm trong tên. Script backup.sh trong /etc/cron.daily/ sẽ không được chạy — hãy đặt tên là backup. Kiểm tra những gì sẽ được thực thi bằng run-parts --test /etc/cron.daily.
Các file trong /etc/cron.d/ phải thuộc về root, không được ghi bởi nhóm và người khác, và kết thúc bằng ký tự xuống dòng — nếu không cron sẽ bỏ qua chúng.
Môi trường của cron: PATH, SHELL và biến
Lý do chính của câu "trong terminal chạy được, còn trong cron thì không" nằm ở môi trường. Cron chạy lệnh qua /bin/sh, không đọc .bashrc và .profile của bạn, và với PATH tối thiểu (thường là /usr/bin:/bin). Vì vậy docker, node hay php nằm trong /usr/local/bin có thể đơn giản là không tìm thấy.
Có thể khai báo biến ngay đầu bảng:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
CRON_TZ=Europe/Moscow
0 6 * * * cd /srv/app && ./report.sh
MAILTO— nơi gửi đầu ra của tác vụ;MAILTO=""tắt việc gửi thư.CRON_TZ— múi giờ cho lịch (được cronie trên RHEL/Fedora hỗ trợ; bản cron của Debian không hỗ trợ biến này).- Trong giá trị biến không có phép thay thế kiểu
$HOME/bin— hãy viết đường dẫn đầy đủ.
Cách đáng tin cậy nhất là dùng đường dẫn tuyệt đối tới mọi chương trình và file trong tác vụ, và đặt shebang #!/bin/bash ở dòng đầu mọi script.
Xem nhật ký cron ở đâu
Cron ghi vào nhật ký việc tác vụ đã được khởi chạy, nhưng không ghi đầu ra của nó:
# Debian/Ubuntu
grep CRON /var/log/syslog
# qua journald của systemd
journalctl -u cron --since today # Debian/Ubuntu
journalctl -u crond --since today # RHEL, Fedora, Rocky
# RHEL không có journald
tail -f /var/log/cron
Dòng ghi nhận việc khởi chạy trông như sau:
Oct 14 03:00:01 web1 CRON[21544]: (root) CMD (/usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1)
Để thấy tác vụ đã in ra gì và lỗi ở đâu, hãy chuyển hướng cả stdout lẫn stderr vào file: >> /var/log/job.log 2>&1. Nếu không, đầu ra sẽ được gửi thư qua mail cục bộ, và nếu không có trình gửi thư (MTA) thì sẽ bị mất (trong nhật ký xuất hiện No MTA installed, discarding output).
Vì sao cron không chạy tác vụ: lỗi thường gặp
Không tìm thấy lệnh
Triệu chứng: trong nhật ký của tác vụ có command not found. Nguyên nhân là PATH bị rút gọn. Cách sửa: dùng đường dẫn đầy đủ tới chương trình (which docker sẽ cho biết) hoặc đặt dòng PATH= ở đầu bảng.
Dấu phần trăm trong lệnh
Trong crontab, ký tự % có nghĩa là xuống dòng: mọi thứ sau % đầu tiên được chuyển cho lệnh qua đầu vào chuẩn. Vì thế dòng kinh điển với ngày trong tên file bị hỏng:
# sai — lệnh bị cắt ở % đầu tiên
0 2 * * * tar czf /backup/site-$(date +%F).tar.gz /var/www
# đúng — escape bằng dấu \
0 2 * * * tar czf /backup/site-$(date +\%F).tar.gz /var/www
Cách đơn giản hơn là đưa logic vào script, nơi không cần escape %.
Script không thực thi được hoặc sai ký tự xuống dòng
Lỗi Permission denied nghĩa là file không có bit x: chạy chmod +x script.sh hoặc gọi rõ ràng /bin/bash /opt/script.sh. Nếu script được viết trên Windows, ký tự xuống dòng CRLF gây lỗi lạ /bin/bash^M: bad interpreter — sửa bằng sed -i 's/\r$//' script.sh hoặc dos2unix.
Đường dẫn tương đối và thư mục làm việc
Tác vụ bắt đầu trong thư mục home của người dùng. Nếu script đọc ./config.ini, nó sẽ tìm file này trong ~. Hãy thêm cd /srv/app && trước lệnh hoặc dùng đường dẫn tuyệt đối trong script.
Ngày trong tháng và thứ trong tuần cùng được chỉ định
Khi cả hai trường đều được đặt, cron chạy khi khớp một trong hai, chứ không phải cả hai. Dòng 0 9 13 * 5 sẽ chạy vào ngày 13 và mỗi thứ Sáu, không chỉ thứ Sáu ngày 13. Điều kiện mong muốn nên kiểm tra bên trong lệnh:
0 9 13 * * [ "$(date +\%u)" = 5 ] && /opt/friday13.sh
Các tác vụ chồng lên nhau
Nếu việc sao lưu chạy lâu hơn khoảng thời gian lặp, lần chạy tiếp theo sẽ bắt đầu chồng lên lần trước. Cách phòng tránh là khóa bằng flock:
*/10 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/sync.sh
Với cờ -n, bản thứ hai sẽ thoát ngay nếu bản đầu tiên vẫn đang chạy.
Daemon cron không chạy
Kiểm tra bằng systemctl status cron (trên RHEL là crond). Trong các image tối giản, ví dụ container Docker, thường không có daemon cron — khi đó các tác vụ trong crontab không có ai thực thi.
Cách gỡ lỗi tác vụ cron
Chạy lệnh trong một môi trường giống cron nhất có thể:
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=$HOME /bin/sh -c '/opt/backup.sh'
Nếu lỗi xuất hiện ở đây, nó cũng sẽ xuất hiện trong cron. Tạm thời đặt lịch * * * * *, chuyển hướng đầu ra vào file và theo dõi bằng tail -f. Để kiểm tra chính biểu thức lịch có các trang tính toán trực tuyến, nhưng nhật ký hệ thống mới là căn cứ cuối cùng.
Ai được dùng cron: cron.allow và cron.deny
Quyền truy cập được giới hạn bởi các file /etc/cron.allow và /etc/cron.deny. Nếu tồn tại cron.allow, chỉ những người dùng được liệt kê trong đó mới có crontab. Nếu không có nó nhưng có cron.deny, thì mọi người trừ những người được liệt kê đều được dùng. Người bị cấm khi chạy crontab -e sẽ nhận thông báo You (bob) are not allowed to use this program.
Tóm lại
Crontab là các dòng gồm năm trường thời gian và một lệnh: */5 * * * * — mỗi 5 phút, 0 3 * * * — mỗi đêm, 0 9 * * 1-5 — vào ngày thường. Sửa bảng qua crontab -e, dùng đường dẫn tuyệt đối, escape %, ghi đầu ra vào nhật ký và bảo vệ các tác vụ dài bằng flock. Các ví dụ crontab trên đủ cho phần lớn công việc trên máy chủ, còn tự động hóa và lập lịch tác vụ được thực hành cụ thể trong khóa học Linux.
Câu hỏi thường gặp
cron khác anacron ở điểm nào?
Cron chạy tác vụ đúng giờ và bỏ qua chúng nếu máy đã tắt. Anacron đảm bảo các tác vụ hằng ngày, hằng tuần và hằng tháng vẫn được thực hiện sau khi máy bật lại, nên phù hợp với laptop và máy để bàn.
Nên dùng cron hay timer của systemd?
cron đơn giản hơn và có hầu như ở mọi nơi. Timer của systemd cho nhật ký trong journalctl, quản lý phụ thuộc, giới hạn tài nguyên và tham số Persistent=true để chạy bù các lần bị bỏ lỡ, nên trên máy chủ hiện đại người ta ngày càng chọn nó cho các tác vụ quan trọng.
Crontab của người dùng được lưu ở đâu?
Trên Debian và Ubuntu là /var/spool/cron/crontabs/tên-người-dùng, trên RHEL và Fedora là /var/spool/cron/tên-người-dùng. Không nên sửa trực tiếp các file này, hãy dùng crontab -e.
Làm sao tạm tắt một tác vụ trong crontab?
Thêm ký tự # ở đầu dòng đó qua crontab -e. Để tắt toàn bộ tác vụ của một người dùng, lưu bảng ra file bằng crontab -l, rồi xóa bảng bằng crontab -r.
Làm sao chạy tác vụ vào ngày cuối cùng của tháng?
Không có trường riêng cho việc này. Hãy chạy tác vụ vào các ngày 28–31 và kiểm tra ngày mai có phải ngày 1 hay không: 0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /opt/job.sh.
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Linux: с нуля до уверенного администрирования”.
Đến khóa học