systemd timer thay cron trên Linux server

systemd timer thay cron: chạy task định kỳ chuẩn

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 journalctl thay 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ền root hoặc sudo. 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ì root nếu không cần quyền cao.
  • NiceIOScheduling*: 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. Theo systemd.timer(5), option này có tác dụng với timer dạng OnCalendar.
  • 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.targettime-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:

  1. Viết script idempotent.
  2. Tạo .service dạng Type=oneshot.
  3. Test service bằng systemctl start.
  4. Tạo .timer với OnCalendar.
  5. Validate lịch bằng systemd-analyze calendar.
  6. Enable timer và monitor bằng list-timers + journalctl.

Nguồn tham khảo

Leave a Comment

Your email address will not be published. Required fields are marked *