Skip to content
VDS · Ứng dụng và bot

Tác vụ theo lịch: cron và systemd timer

Cách chạy tác vụ theo lịch, vì sao tác vụ chạy thủ công được nhưng im lặng trong cron và khi nào nên chuyển sang systemd timer.

Cập nhật 23 tháng 8, 2026

Sao lưu, gia hạn chứng chỉ, dọn dẹp tệp tạm, báo cáo hằng đêm — tất cả đều là tác vụ theo lịch. Trên Linux bạn giải quyết chúng theo hai cách: cron cổ điển và systemd timer.

Cron: bắt đầu nhanh

Bạn chỉnh lịch của một người dùng như sau:

crontab -e
crontab -l

Định dạng dòng gồm năm trường và một lệnh:

# min  hour  day  month  day-of-week  command
  0    4    *     *      *            /usr/local/bin/backup.sh
  */15 *    *     *      *            /usr/local/bin/check.sh
  30   3    *     *      0            /usr/bin/systemctl restart myapp

Vài cách viết tắt hữu ích: @daily, @hourly, @reboot. Để kiểm tra xem các job có chạy hay không:

grep CRON /var/log/syslog | tail -30
journalctl -u cron --since "1 hour ago"

Vì sao script chạy tay được mà trong cron lại không

Đây là vấn đề phổ biến nhất, và nguyên nhân hầu như luôn nằm ở một trong ba điểm sau.

Môi trường khác. Cron có PATH tối giản và không có biến nào từ .bashrc của bạn. Hãy dùng đường dẫn đầy đủ tới chương trình và khai báo biến tường minh ở đầu script hoặc trong crontab:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Thư mục khác. Job khởi chạy trong thư mục home của người dùng, nên đường dẫn tương đối sẽ trỏ tới nơi khác. Luôn dùng đường dẫn tuyệt đối.

Kết quả xuất không đi đâu cả. Đã có lỗi, nhưng không ai thấy. Hãy ghi log:

0 4 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

Thêm hai điều nhỏ: ký tự % trong dòng cron phải được escape thành \%, và tệp crontab phải kết thúc bằng một dòng mới — nếu không, job cuối cùng sẽ bị bỏ qua.

Bạn cần xác minh từ bên ngoài rằng một job đã chạy. Một cron im lặng trông y hệt như một cron thành công. Hãy thiết lập kiểm tra push trong Uptime Kuma: script ping tới một địa chỉ ở cuối, và nếu cả ngày không có ping nào, bạn sẽ nhận được cảnh báo.

systemd timer

Khi tác vụ phức tạp hơn một lệnh đơn, timer sẽ tiện hơn: nó có journal, phụ thuộc, giới hạn tài nguyên và bảo vệ chống hai bản chạy cùng lúc.

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run nightly backup

[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
RandomizedDelaySec=15m

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers
journalctl -u backup.service -n 50

Persistent=true sẽ chạy bù lần bị lỡ nếu máy đã tắt — cron không làm được điều đó. RandomizedDelaySec giúp dàn đều tải để hàng chục tác vụ không khởi chạy cùng một giây.

Những điều cần lưu ý

  • Thời gian của máy mặc định là UTC. Hãy kiểm tra timedatectl trước khi bạn đặt “4 giờ sáng”.
  • Các tác vụ nặng (nén, kiểm tra sao lưu) bị giới hạn bởi ổ đĩa. Xem tốc độ ổ đĩa và lên lịch chúng vào ban đêm.
  • Một tác vụ dài chạy mỗi năm phút sẽ chồng lên chính nó. Trong systemd, Type=oneshot giúp ích; trong cron, hãy dùng lock qua flock:
*/5 * * * * /usr/bin/flock -n /tmp/job.lock /usr/local/bin/job.sh