Server Task Scheduling with cron and systemd Timers
You will schedule a recurring server task with both cron and a systemd timer, adding logging and missed-run catch-up. By the end you will have observable automation that survives reboots and tells you when it fails.
Learning Objectives
- Write a logged cron entry with a sane environment.
- Create an equivalent systemd timer with Persistent catch-up.
- Alert on failure rather than failing silently.
- Time: ~2 hours · Difficulty: Beginner · Prereqs: a systemd-based server.
Architecture Overview
graph LR
Timer[systemd timer OnCalendar] -->|triggers| Svc[backup.service]
Svc -->|runs| Job[Script]
Job -->|logs| J[journald]
Job -->|nonzero exit| Alert[OnFailure notify]
Environment Setup
You will need: a systemd server and a script to schedule (for example the restic backup from the backup lab).
Before you begin: use absolute paths in scheduled jobs; cron's environment is minimal.
Step-by-Step Execution
01
Add a logged cron entry
Redirecting both streams to a log makes cron failures visible instead of silent.
( crontab -l 2>/dev/null; echo "30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1" ) | crontab -02
Create a systemd timer with catch-up
Persistent=true runs a missed job after downtime, which plain cron will not do.
printf '[Timer]\nOnCalendar=*-*-* 02:30:00\nPersistent=true\n[Install]\nWantedBy=timers.target\n' | sudo tee /etc/systemd/system/backup.timer[ROOT REQUIRED] Pairs with a backup.service unit.
03
Enable and inspect
# systemctl daemon-reload && systemctl enable --now backup.timer && systemctl list-timers backup.timer
NEXT LEFT UNIT ACTIVATES
2026-06-18 02:30 UTC 16h backup.timer backup.service
Progress So Far
graph LR
A[01 Cron entry] -->|done| B[02 systemd timer]
B -->|done| C[03 Enable + inspect]
style A fill:#1a4a1a,stroke:#00ff00,color:#fff
style B fill:#1a4a1a,stroke:#00ff00,color:#fff
style C fill:#1a4a1a,stroke:#00ff00,color:#fff
Testing & Validation
sudo systemctl start backup.service && journalctl -u backup.service -n 5 --no-pagerYou should see the job run and log output. If the timer also appears in list-timers with a future run, scheduling is working.
Troubleshooting
- Cron job runs in shell but not scheduled: cron has a minimal PATH; use absolute paths and set variables.
- Timer never fires: confirm it is enabled and the
OnCalendarsyntax withsystemd-analyze calendar. - Missed runs: set
Persistent=trueso the job catches up after downtime.
Extension Ideas
- Add an
OnFailure=unit that sends a notification. - Schedule the restic backups from the backup lab.
- Randomize start with
RandomizedDelaySecto avoid thundering herds.
Key Results
- Scheduled a task via both cron and a systemd timer.
- Captured all job output to logs for observability.
- Enabled missed-run catch-up with Persistent timers.
- Validated the schedule with list-timers and a manual run.