Tự động hóa tác vụ với cron và systemd timer trên Linux
Tự động hóa tác vụ với cron và systemd timer trên Linux
Backup database lúc 2h sáng, dọn log cũ mỗi Chủ nhật, đồng bộ dữ liệu mỗi 5 phút, gửi báo cáo doanh thu 8h sáng thứ Hai — server Linux nào cũng có cả tá việc lặp lại như vậy. Câu hỏi không phải "có tự động hóa không" mà là "tự động hóa bằng gì": cron truyền thống đã dùng 40 năm, hay systemd timer hiện đại mà mọi distro Linux ngày nay đều có sẵn?
Bài viết này so sánh cron và systemd timer trên Linux từ cú pháp cơ bản đến tình huống thực tế: khi nào cron vẫn đủ, khi nào nên chuyển sang systemd timer, và cách migrate từng bước không làm gián đoạn hệ thống đang chạy.
Cron systemd timer linux: cron là gì? Ôn lại trong 5 phút
cron là trình lập lịch tác vụ kinh điển của Unix/Linux, chạy nền dưới dạng daemon (crond hoặc cron.service). Bạn khai báo "lịch" trong file crontab, cron sẽ thực thi lệnh đúng giờ — đơn giản, nhẹ, có mặt trên mọi distro từ VPS 512 MB RAM tới server enterprise.
Mỗi dòng crontab có 6 trường:
* * * * * <lệnh>
│ │ │ │ │ └─ lệnh cần chạy
│ │ │ │ └─── thứ trong tuần (0-7, 0 và 7 đều là Chủ nhật)
│ │ │ └───── tháng (1-12)
│ │ └─────── ngày trong tháng (1-31)
│ └───────── giờ (0-23)
└─────────── phút (0-59)
Ví dụ thực tế cho admin:
# Backup database mỗi ngày lúc 2h sáng
0 2 * * * /opt/backup/mysql-backup.sh >> /var/log/backup.log 2>&1
# Dọn file log cũ hơn 30 ngày, mỗi Chủ nhật 3h sáng
0 3 * * 0 find /var/log/myapp -name "*.log" -mtime +30 -delete
# Kiểm tra disk mỗi 5 phút
*/5 * * * * /opt/monitor/check-disk.sh
# Gửi báo cáo 8h sáng thứ 2 hàng tuần
0 8 * * 1 /opt/reports/weekly.sh
Quản lý crontab: crontab -e (sửa), crontab -l (xem), crontab -r (xóa — cẩn thận, không hỏi lại). Trên hệ thống nhiều user, mỗi user có crontab riêng; tác vụ hệ thống đặt trong /etc/crontab hoặc /etc/cron.d/ (có thêm trường user).
Cron systemd timer linux: cú pháp cron và ví dụ thực tế
Ngoài 5 trường thời gian cơ bản, cron hỗ trợ các ký hiệu đặc biệt:
| Ký hiệu | Ý nghĩa | Ví dụ |
|---|---|---|
* |
Mọi giá trị | * * * * * = mỗi phút |
, |
Liệt kê | 0 8,12,18 * * * = 8h, 12h, 18h |
- |
Khoảng | 0 9-17 * * 1-5 = 9h–17h các ngày trong tuần |
/ |
Bước nhảy | */15 * * * * = mỗi 15 phút |
@reboot |
Khi khởi động | @reboot /opt/app/start.sh |
@daily |
Mỗi ngày 0h | Tương đương 0 0 * * * |
Ba quy tắc vàng khi viết cron:
1. Luôn dùng đường dẫn tuyệt đối. Môi trường cron rất "nghèo" — PATH tối thiểu, không có biến môi trường của user. Lệnh python3 script.py có thể chạy tay ngon lành nhưng cron báo "command not found". Cách an toàn: /usr/bin/python3 /opt/app/script.py, hoặc khai báo PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin đầu crontab.
2. Luôn ghi log. cron gửi output qua email (thường không ai đọc). Thêm >> /var/log/job.log 2>&1 vào mọi dòng quan trọng — khi job lỗi lúc 2h sáng, log là thứ duy nhất cứu bạn.
3. Chống chạy chồng (overlap). Job chạy mỗi 5 phút nhưng có lúc tốn 8 phút → hai tiến trình chồng lên nhau, tranh tài nguyên, corrupt dữ liệu. Dùng flock để khóa:
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/app/sync.sh
flock -n nghĩa là nếu đã có tiến trình giữ lock thì bỏ qua lần chạy này — đơn giản mà hiệu quả.
Cron systemd timer linux: giới hạn của cron mà ít người nói
cron đơn giản, nhưng sự đơn giản đó có giá — những giới hạn chỉ lộ ra khi hệ thống lớn lên:
Không biết job có chạy thành công không. cron chỉ "bắn và quên" (fire-and-forget). Job crash giữa chừng, server reboot đúng giờ job chạy, hay script treo — cron không báo, không thử lại, không lưu trạng thái. Bạn chỉ phát hiện khi backup... không có file backup.
Không chạy bù (catch-up). Server tắt 2h–4h sáng (bảo trì), job backup lúc 2h30 bị lỡ — cron bỏ qua luôn, không chạy bù khi server lên lại. Với backup, một lần lỡ có thể nghĩa là mất cả ngày dữ liệu.
Log phân tán. Mỗi job tự lo log của mình (nếu có). Muốn biết "đêm qua những job nào chạy, job nào lỗi" phải mò từng file log — không có cái nhìn tổng thể.
Không quản lý phụ thuộc. Job B cần chạy sau khi job A xong? cron không hỗ trợ — phải tự canh giờ (A chạy 2h, B chạy 2h30, cầu mong A xong trước 2h30) hoặc viết wrapper script phức tạp.
Độ chính xác thời gian. cron có độ phân giải 1 phút — không lập lịch theo giây được. Và thời điểm chạy là "xấp xỉ": nếu hệ thống bận, job có thể trễ vài chục giây mà không ai hay.
Với 5–10 job đơn giản, những giới hạn này chấp nhận được. Với 50+ job quan trọng (backup, đồng bộ, báo cáo tài chính), bạn cần công cụ mạnh hơn — đó là lúc systemd timer xuất hiện.
systemd timer là gì?
systemd timer là cơ chế lập lịch tích hợp sẵn trong systemd — hệ thống init mặc định của hầu hết distro Linux hiện đại (Ubuntu 16.04+, Debian 8+, CentOS/RHEL 7+, AlmaLinux, Rocky...). Thay vì daemon riêng như cron, timer là một unit của systemd, tận dụng toàn bộ hạ tầng systemd: log tập trung (journal), quản lý phụ thuộc, sandbox bảo mật, và trạng thái rõ ràng.
Một timer gồm hai file đi cặp:
.service— mô tả làm gì (lệnh chạy, user chạy, biến môi trường...).timer— mô tả khi nào (lịch chạy)
Ví dụ: backup database mỗi ngày lúc 2h sáng.
File /etc/systemd/system/db-backup.service:
[Unit]
Description=Backup MySQL database hang ngay
[Service]
Type=oneshot
User=mysql-backup
ExecStart=/opt/backup/mysql-backup.sh
# Chạy trong sandbox: chỉ đọc /var/lib/mysql, chỉ ghi /backup
ProtectSystem=strict
ReadWritePaths=/backup
File /etc/systemd/system/db-backup.timer:
[Unit]
Description=Chay backup database moi ngay 2h sang
[Timer]
OnCalendar=02:00
Persistent=true
Unit=db-backup.service
[Install]
WantedBy=timers.target
Kích hoạt:
systemctl daemon-reload
systemctl enable --now db-backup.timer
systemctl list-timers # xem tất cả timer và lần chạy kế tiếp
Điểm đáng chú ý: Persistent=true — nếu server tắt đúng 2h sáng, khi khởi động lại systemd sẽ chạy bù job bị lỡ. Đây chính là thứ cron không làm được.
Cú pháp lịch OnCalendar
OnCalendar= linh hoạt hơn cron rất nhiều:
| Cú pháp | Ý nghĩa |
|---|---|
OnCalendar=02:00 |
Mỗi ngày 2h sáng |
OnCalendar=*:0/15 |
Mỗi 15 phút |
OnCalendar=Mon *-*-* 08:00 |
8h sáng thứ Hai |
OnCalendar=*-*-01 03:00 |
3h sáng ngày mùng 1 hàng tháng |
OnCalendar=hourly |
Mỗi giờ |
OnCalendar=daily |
Mỗi ngày 0h |
Ngoài OnCalendar, timer còn hỗ trợ lịch theo sự kiện:
- OnBootSec=15min — chạy 15 phút sau khi boot (thay @reboot của cron, nhưng có delay)
- OnUnitActiveSec=1h — chạy mỗi 1 giờ tính từ lần chạy trước xong (cron không làm được: cron tính từ mốc giờ cố định, nếu job tốn 50 phút thì lần sau chỉ cách 10 phút)
Kiểm tra cú pháp trước khi dùng: systemd-analyze calendar "Mon *-*-* 08:00" — hiển thị các lần kích hoạt kế tiếp, tránh viết sai lịch.
So sánh cron vs systemd timer
| Tiêu chí | cron | systemd timer |
|---|---|---|
| Có sẵn | Mọi distro (cài thêm trên minimal) | Mọi distro dùng systemd (mặc định) |
| Độ phân giải | 1 phút | 1 giây |
| Chạy bù khi lỡ (server tắt) | Không | Có (Persistent=true) |
| Chống chạy chồng | Tự làm (flock) |
Có (Type=oneshift + systemd tự quản lý) |
| Log tập trung | Không (tự ghi file) | Có (journalctl -u ten.service) |
| Xem trạng thái/lịch chạy | crontab -l (chỉ xem lịch) |
systemctl list-timers (lịch + lần chạy kế tiếp + lần chạy cuối) |
| Môi trường chạy | Nghèo (tự khai báo PATH) | Đầy đủ, cấu hình rõ trong unit |
| Sandbox bảo mật | Không | Có (ProtectSystem, PrivateTmp...) |
| Phụ thuộc giữa job | Không | Có (After=, Requires=) |
| Độ phức tạp | Rất đơn giản (1 dòng) | Cao hơn (2 file) |
| Gửi mail khi lỗi | Có (MAILTO, ít dùng) | Qua OnFailure + script riêng |

Chuyển từ cron sang systemd timer từng bước
Không cần "đại tu" một lúc. Quy trình migrate an toàn cho từng job:
Bước 1: Chọn job thí điểm. Bắt đầu với job ít quan trọng nhất (ví dụ dọn log), không phải job backup production.
Bước 2: Viết cặp .service/.timer như mẫu ở trên, đặt lịch giống hệt cron cũ.
Bước 3: Chạy thử thủ công. systemctl start db-backup.service (chạy service trực tiếp, không qua timer) → kiểm tra systemctl status và journalctl -u db-backup.service xem có lỗi không.
Bước 4: Chạy song song 1–2 chu kỳ. Bật timer mới nhưng chưa xóa dòng cron cũ — cho job chạy ở chế độ "ghi log riêng" để so sánh kết quả hai bên. (Chỉ áp dụng với job idempotent — chạy 2 lần không gây hại, như dọn log, đồng bộ.)
Bước 5: Cắt cron cũ. Sau 2–3 chu kỳ ổn định, comment dòng cron cũ (đừng xóa vội), theo dõi thêm 1 tuần rồi mới xóa hẳn.
Bước 6: Lặp lại với các job quan trọng dần. Ưu tiên migrate trước: job backup (cần Persistent), job hay bị overlap, job cần giám sát chặt.

Giám sát và log với journalctl
Đây là lợi thế lớn nhất của systemd timer trong vận hành hàng ngày:
# Xem tất cả timer và lịch chạy
systemctl list-timers
# Log của một job cụ thể
journalctl -u db-backup.service
# Log của job chạy gần nhất
journalctl -u db-backup.service -n 50 --no-pager
# Các job thất bại trong 24h qua
systemctl list-units --state=failed
# Theo dõi realtime khi job đang chạy
journalctl -u db-backup.service -f
Mẹo vận hành: đặt OnFailure= trong file .service để tự động gọi một service cảnh báo (gửi Telegram/email) khi job lỗi — tương đương với hệ thống alerting mini mà không cần cài thêm gì:
[Unit]
OnFailure=alert-telegram@db-backup.service
Best practices và lỗi thường gặp
Best practices:
- Đặt tên file rõ ràng theo chức năng: db-backup.service, log-cleanup.service — 6 tháng sau bạn vẫn hiểu ngay.
- Luôn set User= cụ thể, không chạy job bằng root nếu không cần thiết.
- Dùng RandomizedDelaySec=300 cho job chạy trên nhiều server cùng lúc (tránh "thundering herd" — 50 server cùng backup lúc 2h00:00 làm sập storage).
- AccuracySec=1m (mặc định) là đủ cho hầu hết job — đừng set 1s nếu không cần, tốn tài nguyên đánh thức CPU.
Lỗi thường gặp:
1. Quên daemon-reload sau khi sửa file unit → systemd vẫn dùng bản cũ. Luôn chạy systemctl daemon-reload sau mỗi lần sửa.
2. Quên enable — timer chạy tay được (start) nhưng reboot xong mất. Muốn tự chạy sau reboot phải enable.
3. Đường dẫn tương đối trong ExecStart — systemd yêu cầu đường dẫn tuyệt đối, sẽ báo lỗi ngay khi start.
4. Type mặc định là simple — với job chạy một lần rồi thoát, nên dùng Type=oneshot để systemd biết khi nào job xong (quan trọng cho OnUnitActiveSec và phát hiện treo).
5. Nhầm lẫn múi giờ — OnCalendar dùng giờ hệ thống. Server để UTC mà tưởng giờ Việt Nam → job chạy lệch 7 tiếng. Kiểm tra bằng timedatectl.
Kết luận
cron vẫn là lựa chọn tuyệt vời cho 5–10 job đơn giản: một dòng là xong, nhẹ, ai cũng biết. Nhưng khi hệ thống lớn lên — cần chạy bù khi lỡ, chống chạy chồng, log tập trung, giám sát trạng thái — systemd timer trên Linux là bước tiến tự nhiên, lại có sẵn không cần cài thêm.
Lộ trình khuyến nghị: giữ cron cho job đơn giản đang chạy ổn; mọi job mới quan trọng (backup, đồng bộ dữ liệu, báo cáo) viết bằng systemd timer ngay từ đầu; và migrate dần các job cron cũ theo quy trình 6 bước — bắt đầu từ job ít quan trọng nhất.
Dù dùng công cụ nào, nguyên tắc không đổi: mọi tác vụ tự động đều phải có log, có giám sát và có người nhận cảnh báo khi lỗi. Hạ tầng Cloud Server/VPS của SunCloud hỗ trợ đầy đủ systemd trên mọi distro, kèm snapshot backup để bạn thử nghiệm timer thoải mái mà không sợ làm hỏng hệ thống. Hotline 083 979 3434 hoặc email sales@suncloud.vn, website suncloud.vn.
Bài viết liên quan
- Dịch vụ VPS SunCloud
- Cài đặt Docker Compose trên VPS: chạy 10 dịch vụ với 1 file cấu hình
- Cài đặt LEMP Stack trên Ubuntu 24.04 từ A–Z cho người mới
Câu hỏi thường gặp
Có nên bỏ cron hoàn toàn để dùng systemd timer?
Không nhất thiết. cron đủ tốt cho job đơn giản, ít quan trọng. Chiến lược thực tế của nhiều đội vận hành: giữ cron cho việc vặt, dùng systemd timer cho job quan trọng cần giám sát và chạy bù. Quan trọng là biết rõ job nào đang ở đâu — đừng để cron và timer quản lý chồng chéo cùng một việc.
systemd timer có chạy được trên mọi distro không?
Chạy trên mọi distro dùng systemd làm init system: Ubuntu 16.04+, Debian 8+, CentOS/RHEL 7+, AlmaLinux, Rocky Linux, Fedora... Hầu hết VPS hiện nay đều dùng một trong các distro này. Các distro đặc biệt (Alpine dùng OpenRC, Devuan) thì không có systemd — vẫn dùng cron.
Timer có tốn tài nguyên hơn cron không?
Không đáng kể. systemd vốn đã chạy sẵn trên hệ thống; thêm vài timer chỉ là thêm vài unit được đánh thức đúng lịch. Với AccuracySec mặc định (1 phút), systemd còn gộp các lần đánh thức để tiết kiệm pin/CPU.
Làm sao migrate hàng chục dòng cron cùng lúc?
Viết script chuyển đổi: parse từng dòng crontab thành cặp .service/.timer mẫu. Nhưng đừng migrate ồ ạt — mỗi job nên được review lại (có job cron cũ đã... không còn cần thiết nữa). Migrate theo đợt 5–10 job, ưu tiên job quan trọng.
Job chạy lâu hơn chu kỳ thì systemd timer xử lý thế nào?
Mặc định, nếu job chưa xong mà đến lịch tiếp theo, systemd sẽ bỏ qua lần kích hoạt mới (không chạy chồng) — an toàn hơn cron. Muốn hành vi khác (ví dụ xếp hàng đợi), cấu hình thêm, nhưng với đa số trường hợp backup/đồng bộ, "bỏ qua khi đang chạy" chính là hành vi mong muốn.
Có công cụ giao diện web quản lý timer không?
Có một số lựa chọn: Cockpit (có sẵn trên RHEL/CentOS, quản lý systemd qua web), hoặc các nền tảng lớn hơn như Rundeck/Ansible Tower khi cần quản lý tập trung hàng trăm server. Với 1–5 server, dòng lệnh systemctl list-timers + journalctl là đủ và nhanh nhất.
