Bạn có một script backup, cleanup log hoặc sync dữ liệu cần chạy mỗi ngày trên Linux server. Dùng cron thì nhanh, nhưng khi task fail, miss lịch do server reboot, hoặc cần xem log tập trung bằng journalctl, việc debug bắt đầu hơi mệt.
Trong nhiều case production, systemd timer thay cron là lựa chọn gọn hơn: timer có unit riêng, log đi vào journal, có thể cấu hình retry qua service, xem next run bằng systemctl list-timers, và dùng Persistent=true để chạy bù nếu server tắt đúng lúc lịch chạy.
Bài này hướng dẫn cách tạo một systemd timer chạy task định kỳ, kèm ví dụ áp dụng trên Ubuntu 22.04/24.04, Debian 12, AlmaLinux 9. Các distro này dùng systemd phổ biến trong range 249–255, đủ cho các directive trong bài.
Khi nào nên dùng systemd timer thay cron?
cron vẫn ổn cho job đơn giản. Nhưng nếu bạn đang vận hành server production, systemd timer đáng cân nhắc khi cần:
- xem trạng thái task bằng
systemctl status; - gom log bằng
journalctlthay vì file log rời rạc; - chạy bù job bị missed sau reboot với
Persistent=true; - gắn dependency với service khác;
- dùng cùng một style quản lý service như các daemon khác trên server.
Nếu bạn mới bắt đầu chuẩn hóa service, nên đọc thêm bài quản lý Linux service với systemd theo chuẩn production. Timer thực chất chỉ là unit kích hoạt một service theo lịch.
Prerequisites: môi trường áp dụng
Ví dụ bên dưới dùng system-level timer, phù hợp cho server.
Áp dụng tốt cho:
- Ubuntu 22.04 — systemd 249
- Ubuntu 24.04 — systemd 255
- Debian 12 — systemd 252
- AlmaLinux 9 / Rocky Linux 9 — systemd 252
Kiểm tra version trên máy:
systemctl --version
Lưu ý: các lệnh trong bài tạo unit dưới
/etc/systemd/system/, cần quyềnroothoặcsudo. Không copy vào production nếu chưa chỉnh path script và kiểm tra quyền file.
Ví dụ thực tế: chạy script backup mỗi ngày lúc 02:30
Giả sử bạn có script backup ở:
/usr/local/sbin/app-backup.sh
Nội dung demo tối giản:
#!/usr/bin/env bash
set -euo pipefail
echo "[$(date -Is)] start backup"
# TODO: thay bằng lệnh backup thật, ví dụ restic/rclone/rsync
/usr/bin/true
echo "[$(date -Is)] backup done"
Set quyền execute:
sudo chmod 750 /usr/local/sbin/app-backup.sh
sudo chown root:root /usr/local/sbin/app-backup.sh
Với backup production, bạn có thể kết hợp chiến lược rotate log trong bài cấu hình rotate log cho Nginx, Docker và systemd nếu task sinh log riêng ngoài journal.
Tạo service unit cho task
Tạo file service:
sudo nano /etc/systemd/system/app-backup.service
Nội dung:
[Unit]
Description=Application backup job
Documentation=https://man7.org/linux/man-pages/man5/systemd.service.5.html
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/app-backup.sh
User=root
Group=root
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
Giải thích nhanh:
Type=oneshot: phù hợp với job chạy xong rồi thoát.ExecStart: script hoặc command chính.User/Group: user chạy job. Với app cụ thể, nên dùng service account riêng thay vìrootnếu không cần quyền cao.NicevàIOScheduling*: giảm ưu tiên CPU/I/O để backup không giành tài nguyên với service chính.
Test service trước khi tạo timer:
sudo systemctl daemon-reload
sudo systemctl start app-backup.service
sudo systemctl status app-backup.service --no-pager
Xem log:
sudo journalctl -u app-backup.service -n 100 --no-pager
Nếu service chạy fail, sửa service/script trước. Đừng enable timer khi service còn lỗi.
Tạo timer unit với OnCalendar
Tạo file timer:
sudo nano /etc/systemd/system/app-backup.timer
Nội dung:
[Unit]
Description=Run application backup daily
Documentation=https://man7.org/linux/man-pages/man5/systemd.timer.5.html
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=10min
AccuracySec=1min
Unit=app-backup.service
[Install]
WantedBy=timers.target
Các directive quan trọng:
OnCalendar=*-*-* 02:30:00: chạy mỗi ngày lúc 02:30 theo wall-clock time.Persistent=true: nếu server tắt/reboot và missed lịch chạy, systemd sẽ chạy bù khi timer active lại. Theosystemd.timer(5), option này có tác dụng với timer dạngOnCalendar.RandomizedDelaySec=10min: thêm delay ngẫu nhiên tối đa 10 phút, hữu ích nếu nhiều server cùng chạy backup để tránh spike.AccuracySec=1min: systemd được phép coalesce timer trong khoảng này; không nên kỳ vọng chạy chính xác từng giây.Unit=app-backup.service: service được kích hoạt. Nếu bỏ dòng này, systemd mặc định tìm service cùng tên với timer.
Validate lịch chạy trước khi enable
Trước khi enable timer, kiểm tra syntax OnCalendar:
systemd-analyze calendar '*-*-* 02:30:00'
Bạn sẽ thấy thời điểm normalized và next elapse. Có thể test các lịch khác:
systemd-analyze calendar 'Mon..Fri 22:30'
systemd-analyze calendar 'weekly'
systemd-analyze calendar '*-*-01 03:00:00'
Enable timer:
sudo systemctl daemon-reload
sudo systemctl enable --now app-backup.timer
Xem danh sách timer:
systemctl list-timers --all | grep app-backup
Xem chi tiết:
systemctl status app-backup.timer --no-pager
Chạy thử ngay mà không đợi lịch
Timer chỉ kích hoạt service theo lịch. Nếu muốn test job ngay:
sudo systemctl start app-backup.service
Sau đó xem log:
sudo journalctl -u app-backup.service -n 100 --no-pager
Nếu muốn test timer path, có thể đổi tạm OnCalendar sang vài phút tới, chạy daemon-reload, restart timer rồi xem list-timers. Đừng quên revert lịch sau khi test.
OnCalendar hay OnUnitActiveSec?
Có 2 kiểu timer hay gặp:
1. Calendar timer
Dùng khi bạn muốn job chạy theo giờ/ngày cụ thể:
[Timer]
OnCalendar=Mon..Fri 02:30:00
Persistent=true
Phù hợp cho backup, report, cleanup định kỳ.
2. Monotonic timer
Dùng khi bạn muốn job chạy theo khoảng thời gian tính từ lần active trước:
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
Ví dụ: sau khi boot 5 phút thì chạy, rồi cứ mỗi 1 giờ chạy lại. Theo systemd.timer(5), nhóm OnBootSec, OnStartupSec, OnUnitActiveSec là monotonic timers, không phụ thuộc wall-clock timezone như OnCalendar.
Lỗi thường gặp khi dùng systemd timer
Timer active nhưng service không chạy lại
Nếu service trước đó vẫn active, timer sẽ không spawn thêm một instance mới. systemd.timer(5) ghi rõ: nếu unit cần kích hoạt đã active tại thời điểm timer elapse, nó không bị restart mà được giữ nguyên.
Với job định kỳ, thường nên dùng Type=oneshot và đảm bảo script thoát sau khi chạy xong.
Quên daemon-reload sau khi sửa unit
Sau khi sửa file trong /etc/systemd/system/, luôn chạy:
sudo systemctl daemon-reload
Nếu không, systemd có thể vẫn dùng definition cũ.
Lịch chạy sai do timezone hoặc clock chưa sync
Kiểm tra timezone:
timedatectl
Timer dùng OnCalendar phụ thuộc wall-clock time. Trên máy không có RTC hoặc clock sync chậm, timer calendar có thể chạy lệch. Tài liệu systemd.timer(5) cũng nhắc timer có OnCalendar được order sau time-set.target và time-sync.target để giảm rủi ro clock chưa sẵn sàng.
Persistent=true không chạy bù
Persistent=true chỉ có ý nghĩa với OnCalendar. Nếu bạn dùng OnUnitActiveSec, đừng kỳ vọng nó catch up job bị missed giống calendar timer.
Kiểm tra timer có đang enable không:
systemctl is-enabled app-backup.timer
systemctl status app-backup.timer --no-pager
Job cần environment variable
Không nên phụ thuộc vào shell profile như .bashrc. Khai báo rõ trong service:
[Service]
Environment="APP_ENV=production"
EnvironmentFile=-/etc/default/app-backup
ExecStart=/usr/local/sbin/app-backup.sh
Dấu - trước EnvironmentFile nghĩa là file không tồn tại thì không fail unit.
Có nên bỏ cron hoàn toàn?
Không cần cực đoan. cron vẫn tốt cho task nhỏ, đơn giản, ít cần debug. Nhưng với server production, systemd timer thường đáng dùng cho những job quan trọng vì bạn có:
- status rõ ràng;
- log tập trung;
- dependency với systemd unit khác;
- catch-up missed run bằng
Persistent=true; - kiểm tra next run bằng
list-timers.
Nếu task ảnh hưởng dữ liệu như backup/database cleanup, systemd timer giúp trace lỗi tốt hơn cron rất nhiều.
FAQ
systemd timer có thay cron hoàn toàn không?
Có thể thay trong nhiều case, đặc biệt trên Linux server dùng systemd. Nhưng cron vẫn phù hợp với job nhỏ, ít dependency và không cần quản lý trạng thái phức tạp.
Persistent=true dùng để làm gì?
Persistent=true giúp timer dạng OnCalendar chạy bù nếu lịch chạy bị missed khi máy tắt hoặc timer chưa active. Nó không áp dụng cho monotonic timer như OnUnitActiveSec.
Làm sao xem systemd timer chạy lần kế tiếp khi nào?
Dùng lệnh systemctl list-timers --all. Cột NEXT cho biết lần chạy kế tiếp, LEFT cho biết còn bao lâu nữa.
Sửa file timer xong có cần restart không?
Có. Sau khi sửa unit, chạy sudo systemctl daemon-reload, rồi restart timer bằng sudo systemctl restart ten.timer.
systemd timer có chạy chính xác từng giây không?
Không nên kỳ vọng chính xác từng giây. Timer chịu ảnh hưởng bởi AccuracySec và scheduling của systemd. Với job production, hãy thiết kế job idempotent thay vì phụ thuộc vào giây chính xác.
Kết luận
Nếu bạn chỉ cần chạy một command đơn giản mỗi ngày, cron vẫn đủ. Nhưng khi task quan trọng hơn — backup, cleanup, sync, report production — systemd timer thay cron giúp dễ vận hành hơn nhờ log, status, lịch chạy rõ ràng và khả năng chạy bù với Persistent=true.
Checklist triển khai nhanh:
- Viết script idempotent.
- Tạo
.servicedạngType=oneshot. - Test service bằng
systemctl start. - Tạo
.timervớiOnCalendar. - Validate lịch bằng
systemd-analyze calendar. - Enable timer và monitor bằng
list-timers+journalctl.
