Cron trên Linux là bộ lập lịch đơn giản nhưng rất mạnh để chạy backup, rotate log, đồng bộ dữ liệu, gọi queue worker hoặc thực hiện health check theo thời gian. Phần khó không nằm ở việc nhớ năm dấu * mà ở môi trường chạy khác với terminal: cron có PATH tối giản, không nạp profile tương tác, chạy dưới user cụ thể và thường gửi lỗi vào log thay vì hiện ngay trên màn hình.
Bài này xây dựng một quy trình thực tế cho Ubuntu và Linux nói chung: đọc đúng cú pháp crontab, viết command có đường dẫn tuyệt đối, kiểm soát timezone và biến môi trường, redirect stdout/stderr, tìm log, kiểm tra quyền, rồi debug job không thực thi. Các ví dụ ưu tiên an toàn cho VPS production và có thể áp dụng cho Laravel, script Python, Node.js hoặc lệnh backup.
1. Cron và crontab khác nhau thế nào?
Cron là daemon chạy nền, thường kiểm tra các bảng lịch và quyết định job nào cần chạy. Crontab là bảng cấu hình lịch của từng user hoặc hệ thống. Mỗi user có thể có crontab riêng; command trong đó chạy với quyền của user sở hữu bảng. Vì vậy một job chạy được khi bạn đăng nhập bằng tài khoản shell chưa chắc chạy được dưới cron nếu file, thư mục hoặc socket yêu cầu quyền khác.
Manual cron(8) mô tả daemon sẽ kiểm tra các crontab và chạy job khi thời điểm phù hợp. Manual crontab(1) mô tả lệnh cài đặt, sửa, liệt kê và xóa bảng lịch. Tách hai khái niệm này giúp bạn biết nên kiểm tra service cron hay kiểm tra nội dung crontab.
Mỗi user có thể định nghĩa crontab của riêng mình và các command được thực thi dưới user sở hữu crontab.Tóm lược từ cron(8) và crontab(5) manual.[1] [2]
2. Cú pháp năm trường thời gian
Một dòng cron thông thường có năm trường thời gian rồi đến command: phút, giờ, ngày trong tháng, tháng và ngày trong tuần. Dấu * nghĩa là mọi giá trị hợp lệ. Có thể dùng range, list và step; ví dụ 0-23/2 chạy cách hai giờ trong phạm vi giờ. Không nên đọc biểu thức bằng cảm tính khi lịch quan trọng; hãy tách từng trường và kiểm tra timezone của máy.
# phút giờ ngày-trong-tháng tháng ngày-trong-tuần command
0 2 * * * /usr/local/bin/backup.sh
*/15 * * * * /usr/local/bin/healthcheck.sh
30 9 * * 1-5 /usr/local/bin/report.sh
0 0 1 * * /usr/local/bin/monthly-cleanup.shManual crontab(5) xác nhận phút nằm trong 0–59, giờ 0–23, ngày trong tháng 1–31, tháng 1–12 và ngày trong tuần 0–7 với Chủ nhật có thể biểu diễn bằng 0 hoặc 7. Trường day-of-month và day-of-week có quy tắc OR khi cả hai cùng bị giới hạn, đây là nguyên nhân khiến nhiều lịch “ngày đầu tháng hoặc thứ Hai” chạy nhiều lần hơn dự kiến.
3. Các mẫu lịch thường dùng
Để giảm lỗi, hãy viết lịch theo ý nghĩa vận hành thay vì chỉ sao chép một chuỗi. Lịch backup hàng đêm nên có log, timeout và cơ chế giữ bản cũ; health check chạy mỗi phút nên nhẹ và không tạo hàng nghìn file log. Với job nặng, nên chọn thời điểm ít tải và thêm lock để ngăn hai phiên chạy chồng nhau.
# Mỗi ngày lúc 02:00
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Mỗi 15 phút, từ thứ Hai đến thứ Sáu
*/15 * * * 1-5 /usr/local/bin/check-endpoint.sh >> /var/log/healthcheck.log 2>&1
# Ngày 1 và 15 hàng tháng lúc 04:30
30 4 1,15 * * /usr/local/bin/report.shNếu cần “mỗi 30 ngày” hoặc “sau khi job trước hoàn tất” thì cron thuần không diễn đạt chính xác ý định đó. Có thể dùng systemd timer, một script tự kiểm soát thời gian hoặc scheduler của ứng dụng. Đừng dùng cron cho job cần dependency phức tạp, retry có trạng thái hoặc đảm bảo không bỏ lịch khi máy tắt nếu bạn chưa thiết kế lớp bù.
4. Cài đặt và kiểm tra daemon cron trên Ubuntu
Trên Ubuntu Server, trước tiên hãy xác minh daemon cron đang tồn tại và hoạt động. Một số image tối giản có thể chưa cài gói cron hoặc service đang bị disable. Kiểm tra thay đổi trạng thái trước khi sửa crontab để tránh mất thời gian debug sai lớp.
command -v cron || true
dpkg -l cron 2>/dev/null | grep '^ii' || true
sudo systemctl status cron --no-pager
sudo systemctl is-enabled cron
sudo systemctl is-active cronNếu chưa có gói, cài bằng package manager theo chính sách của máy rồi enable/start service. Sau đó dùng crontab -e để sửa crontab user hiện tại. Với system crontab như /etc/crontab hoặc /etc/cron.d/, format có thêm trường user sau năm trường thời gian; không được bê nguyên dòng user crontab sang file system mà bỏ qua khác biệt này.
5. Môi trường cron khác terminal ở đâu?
Cron thường không đọc toàn bộ .bashrc, .profile hoặc môi trường của phiên SSH. PATH có thể ngắn, thư mục làm việc không phải project directory và biến như HOME, SHELL, LOGNAME có giá trị khác mong đợi. Các lệnh gọi bằng tên ngắn như php, node, composer hoặc python có thể chạy được trong terminal nhưng thất bại trong cron vì không tìm thấy executable.
# Đặt biến tối thiểu ngay trong crontab khi cần
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# Luôn dùng đường dẫn tuyệt đối và cd rõ ràng
0 3 * * * cd /srv/example && /usr/bin/php artisan schedule:run >> /var/log/example-cron.log 2>&1Với Node.js hoặc Python dùng virtualenv, hãy ghi rõ đường dẫn interpreter. Với Laravel, gọi đúng PHP binary, đặt working directory là thư mục project và bảo đảm user cron có quyền đọc .env, ghi storage và truy cập database/socket cần thiết. Bài systemd service cho Node.js, Python và worker hữu ích khi job cần môi trường, restart policy hoặc log quản lý chặt hơn cron.
6. Dấu phần trăm và shell quoting: lỗi khó thấy
Trong crontab của Vixie cron, ký tự % trong command có ý nghĩa đặc biệt: phần trước % được chạy như command, các phần sau được chuyển thành stdin và % được đổi thành newline nếu không escape. Vì vậy lệnh date +%F có thể bị cắt nếu viết trực tiếp trong crontab. Đây là lỗi thường chỉ xuất hiện trong cron dù copy command vào terminal lại chạy đúng.
# Sai hoặc dễ gây lỗi trong crontab
0 1 * * * /usr/bin/date +%F >> /tmp/today.log 2>&1
# Escape phần trăm
0 1 * * * /usr/bin/date +\%F >> /tmp/today.log 2>&1
# Cách dễ đọc hơn: đưa logic vào script
0 1 * * * /usr/local/bin/write-date.sh >> /var/log/write-date.log 2>&1Tách logic vào script có shebang rõ ràng giúp test độc lập, version control và logging tốt hơn. Khi cần nhiều quote, pipe hoặc heredoc, script riêng thường an toàn hơn một dòng cron dài. Sau khi sửa crontab, hãy kiểm tra lại bằng crontab -l và chạy script dưới đúng user để tái hiện môi trường.
7. Quyền file, working directory và lock chống chạy chồng
Ba lỗi phổ biến là script không executable, đường dẫn tương đối trỏ nhầm nơi và job mới bắt đầu khi phiên trước chưa xong. Kiểm tra bằng user thực tế, không chỉ bằng root. Nếu backup mất 40 phút mà cron kích hoạt mỗi 15 phút, có thể sinh nhiều phiên cạnh tranh tài nguyên hoặc làm hỏng file tạm.
sudo -u deploy /usr/local/bin/backup.sh
test -x /usr/local/bin/backup.sh && echo executable
sudo -u deploy sh -lc 'cd /srv/example && /usr/bin/php artisan about'
# Khóa đơn giản bằng flock
*/10 * * * * /usr/bin/flock -n /run/example-job.lock /usr/local/bin/example-job.sh >> /var/log/example-job.log 2>&1Khi dùng lock file, hãy chọn nơi user có quyền ghi và hiểu hành vi khi process chết. flock -n bỏ qua phiên mới nếu lock đang giữ; nếu cần retry, hãy thiết kế retry riêng. Không tự xóa lock mù quáng nếu cơ chế lock dùng file descriptor và process cũ vẫn còn hoạt động.
8. Redirect output và tạo log có thể điều tra
Nếu không redirect, cron có thể gửi output qua mail local hoặc làm mất dấu vết tùy cấu hình. Hãy tách stdout và stderr khi cần phân tích, thêm timestamp trong script hoặc dùng logger. Log phải có retention để không biến chính cron thành nguyên nhân disk full; hãy tham khảo bài xử lý Linux disk full khi xây chính sách log.
# Gộp stdout và stderr
0 * * * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1
# Tách hai luồng
0 * * * * /usr/local/bin/job.sh >> /var/log/job.out 2>> /var/log/job.err
# Ghi qua logger để vào journal/syslog
0 * * * * /usr/local/bin/job.sh 2>&1 | /usr/bin/logger -t example-cronNếu service cron chạy dưới systemd, kiểm tra journal theo unit cron và theo khoảng thời gian. Trên một số hệ thống, log cron được chuyển vào syslog; đừng giả định luôn có file /var/log/cron.log. Hãy kiểm tra logger, journald và cấu hình rsyslog của distro.
9. Cron job không chạy: quy trình debug từng lớp
Đừng sửa cùng lúc lịch, quyền, đường dẫn và script. Hãy debug theo lớp để biết failure nằm ở đâu. Trước tiên xác nhận dòng có trong crontab đúng user; tiếp theo kiểm tra daemon; sau đó chạy command dưới user đó; cuối cùng đọc log tại đúng thời điểm. Một probe đơn giản ghi timestamp vào file giúp phân biệt cron không gọi job với application chạy nhưng thất bại.
crontab -l
sudo systemctl is-active cron
sudo journalctl -u cron --since "30 minutes ago" --no-pager
sudo grep -E 'CRON|CROND' /var/log/syslog 2>/dev/null | tail -50
# Probe tối thiểu trong 5 phút
*/5 * * * * /usr/bin/date -Is >> /tmp/cron-probe.log 2>&1
cat /tmp/cron-probe.logNếu probe chạy nhưng app job không chạy, vấn đề nằm ở command, PATH, quyền, working directory, dependency hoặc logic ứng dụng. Nếu probe không chạy, kiểm tra daemon, syntax, user và thời gian hệ thống. Không dùng “chạy bằng root” như cách chữa mặc định vì điều đó có thể che lỗi permission và tạo rủi ro bảo mật.
10. Timezone, giờ mùa hè và lịch theo múi giờ
Cron dùng timezone của hệ thống hoặc timezone được hỗ trợ bởi daemon/configuration. VPS đặt UTC nhưng người vận hành nghĩ theo GMT+7 sẽ tạo lệch giờ đều đặn. Kiểm tra timezone bằng timedatectl, ghi rõ timezone trong runbook và log timestamp theo ISO 8601. Với lịch quan trọng theo từng khu vực, cân nhắc systemd timer hoặc scheduler ứng dụng có hỗ trợ timezone rõ hơn.
timedatectl
cat /etc/timezone 2>/dev/null || true
date -Is
sudo systemctl show cron --property=EnvironmentKhông chỉ nhìn đồng hồ trong phiên SSH; hãy so sánh timezone, NTP synchronization và timestamp trong log. Nếu máy vừa reboot hoặc clock bị chỉnh, job theo phút có thể bị bỏ qua hoặc chạy khác kỳ vọng. Đặc biệt cần ghi rõ mốc UTC khi schedule backup xuyên nhiều hệ thống.
11. Khi nào dùng cron, khi nào dùng systemd timer?
Cron phù hợp với lịch đơn giản, command độc lập, yêu cầu thấp về dependency và quy trình vận hành quen thuộc. Systemd timer phù hợp khi cần dependency với service, giới hạn tài nguyên, journal logging, missed-run catch-up hoặc quan hệ rõ với unit. Queue scheduler của ứng dụng phù hợp khi job cần database, retry, trạng thái và business context.
- Chọn cron cho script nhỏ, lịch rõ, có redirect log và không cần dependency phức tạp.
- Chọn systemd timer khi muốn quản lý lifecycle, quyền User, resource limit và journal cùng service.
- Chọn queue hoặc scheduler của framework khi job cần retry, lock phân tán, trạng thái và dashboard.
- Dù chọn công cụ nào, vẫn cần monitoring, timeout, retention log và runbook rollback.
12. Checklist production trước khi bật cron
- Xác định user chạy job, đường dẫn tuyệt đối, working directory và timezone.
- Chạy command thủ công dưới đúng user, không chỉ chạy bằng root.
- Kiểm tra daemon cron active và dòng crontab đã được cài đúng bảng.
- Escape dấu phần trăm hoặc chuyển logic phức tạp vào script version-controlled.
- Thêm stdout/stderr redirect, timestamp, retention và cảnh báo khi exit code khác 0.
- Dùng flock hoặc cơ chế lock nếu các phiên có thể chạy chồng.
- Tạo probe và kiểm tra log sau lần chạy đầu tiên.
- Ghi cách disable, chạy lại thủ công và rollback thay đổi cron.
13. Kết luận
Cron không khó vì cú pháp mà khó vì khoảng cách giữa terminal và môi trường daemon. Khi dùng đường dẫn tuyệt đối, kiểm soát user, PATH, timezone, log và lock, cron trở thành một thành phần đáng tin cậy cho các tác vụ nhỏ trên Linux. Khi nhu cầu vượt quá một command theo lịch, hãy chuyển sang systemd timer hoặc queue có trạng thái thay vì kéo dài một dòng crontab đến mức không thể debug.