systemd Timer vs cron: Choosing the Right Scheduler
Which Should You Use?
Default to systemd timer for new workloads. Logs integrate with journalctl, dependencies between services can be enforced, and missed runs are caught on the next boot. Stick with cron when maintaining existing jobs or when a single-line schedule is all you need.
Quick Decision Table
| Scenario | Recommended |
|---|---|
| New periodic job | systemd timer |
| Centralized logs via journalctl | systemd timer |
| Depends on another service | systemd timer |
| Boot-relative trigger needed | systemd timer |
| Maintaining existing cron jobs | Keep cron |
| Simple one-liner on a schedule | cron |
What Is cron?
cron is the traditional UNIX job scheduler. A single line in crontab defines when and what to run, using five fields: minute, hour, day-of-month, month, and day-of-week.
crontab Syntax
min hour dom month dow command
# Every day at 3:00 AM 0 3 * * * /home/user/scripts/backup.sh # Every 5 minutes */5 * * * * /usr/local/bin/collect-logs.sh # Every Monday at 9:00 AM 0 9 * * 1 /usr/local/bin/send-report.sh
crontab Commands
# Open the editor for the current user crontab -e # List current jobs crontab -l # Remove all jobs (use with caution) crontab -r
cron mails output to the local user by default. In environments without a mail daemon, output accumulates in /var/spool/mail. Add MAILTO="" at the top of your crontab to suppress this.
What Is systemd timer?
A systemd timer consists of two unit files: a .timer file (the schedule) and a .service file (the command to run). Both are managed by systemd, so logs, dependencies, and status checks all go through the same interface.
Minimal Example: Run Every 5 Minutes
/etc/systemd/system/collect-logs.service
[Unit]
Description=Collect system logs
[Service]
Type=oneshot
ExecStart=/usr/local/bin/collect-logs.sh
/etc/systemd/system/collect-logs.timer
[Unit]
Description=Run collect-logs every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
Enabling and Monitoring a Timer
# Reload unit files after any change sudo systemctl daemon-reload # Enable and start the timer immediately sudo systemctl enable --now collect-logs.timer # List all active timers with next run times systemctl list-timers # Check execution logs journalctl -u collect-logs.service
Why Is systemd Timer Better?
The two key advantages over cron are centralized logging and fine-grained control.
Logs Go to journalctl
# Last 50 log entries journalctl -u collect-logs.service -n 50 # Logs since today only journalctl -u collect-logs.service --since today # Errors only journalctl -u collect-logs.service -p err
cron output goes to mail or a separate file. systemd timer output lands in the journal alongside all other system events, making incident investigation much faster.
Service Dependencies
[Unit]
Description=Database backup
Requires=postgresql.service
After=postgresql.service
The job only runs if PostgreSQL is active. cron has no equivalent mechanism — you would need to check inside the script instead.
Boot-Relative Triggers
[Timer]
# 10 minutes after boot
OnBootSec=10min
# Then every hour
OnUnitActiveSec=1h
systemd timer supports both calendar-based (OnCalendar) and relative (OnBootSec/OnUnitActiveSec) schedules. cron supports neither.
Catching Missed Runs with Persistent=true
[Timer]
OnCalendar=daily
Persistent=true
If the machine is off at the scheduled time, the job runs on the next boot instead of being skipped silently. cron does not do this without anacron.
How Do You Write OnCalendar?
OnCalendar uses systemd's own timestamp format. Always validate with systemd-analyze calendar before enabling a new timer.
# Validate a schedule and show next trigger times systemd-analyze calendar "Mon *-*-* 03:00:00" # Common patterns OnCalendar=daily # 00:00 every day OnCalendar=hourly # 00:00 every hour OnCalendar=weekly # Mon 00:00 every week OnCalendar=*:0/5 # every 5 minutes OnCalendar=Mon 03:00 # Mon 03:00 every week OnCalendar=*-*-* 03:00:00 # every day at 03:00 (same as above)
Run systemd-analyze calendar before enabling any new timer. It prints the next two scheduled times, catching typos before they cause silent failures.
How Do You Migrate from cron to systemd Timer?
Step 1: Review Existing Jobs
crontab -l # 0 3 * * * /home/user/scripts/backup.sh
Step 2: Create the Service File
sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
Type=oneshot
User=user
ExecStart=/home/user/scripts/backup.sh
Step 3: Create the Timer File
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3:00 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Step 4: Enable and Verify
sudo systemctl daemon-reload sudo systemctl enable --now backup.timer # Confirm the timer is registered systemctl list-timers --all | grep backup # Check logs after the first run journalctl -u backup.service --since today
Step 5: Remove the cron Job
crontab -e # Delete or comment out the migrated line
Confirm the timer is working via systemctl list-timers and journalctl -u backup.service before removing the original cron job. Leaving both active causes double execution.
Comparison Summary
| Feature | cron | systemd timer |
|---|---|---|
| Setup complexity | One line | Two unit files |
| Logging | Mail or file | journalctl |
| Service dependencies | No | Yes |
| Boot-relative trigger | No | Yes (OnBootSec) |
| Catch missed runs | No (needs anacron) | Yes (Persistent=true) |
| User-level jobs | crontab per user | User service units |
| Inspection command | crontab -l |
systemctl list-timers |