Cron vs systemd Timers: Scheduling Jobs on Linux
- cron is a one-line schedule in a crontab: quick, portable and familiar.
- systemd timers need two small unit files but add logging in the journal, catch-up for missed runs, randomised delays and resource limits.
- For a personal one-liner, cron is fine. For anything a server depends on, a timer is easier to monitor and debug.
Scheduling with cron
Each user has a crontab, edited with crontab -e (and listed with crontab -l). Each line is five time fields followed by a command:
# ┌ minute (0-59)
# │ ┌ hour (0-23)
# │ │ ┌ day of month (1-31)
# │ │ │ ┌ month (1-12)
# │ │ │ │ ┌ day of week (0-7, 0 and 7 = Sunday)
# │ │ │ │ │
30 2 * * * /home/alice/bin/backup-home.sh >> /home/alice/backup.log 2>&1
*/15 * * * * /usr/local/bin/check-queue
0 9 * * 1-5 /usr/local/bin/morning-report
@reboot /home/alice/bin/start-tunnel.sh
System-wide jobs go in /etc/crontab or a file in /etc/cron.d/; those have an extra user field before the command. Scripts dropped into /etc/cron.daily/, cron.weekly/ and so on run at those intervals.
The usual cron pitfalls
- Minimal environment. cron runs jobs with a short
PATH(typically/usr/bin:/bin) and none of your shell's settings. Use absolute paths, or setPATH=at the top of the crontab. %is special in a crontab line and must be escaped as\%— a classic trap withdate +%F.- Output disappears. Without a working local mail setup, output and errors are lost. Redirect to a log file as above.
- Missed runs stay missed. If the machine was off at 02:30, the job simply doesn't run (unless you install
anacron). - Not always installed. Debian and Ubuntu install cron by default; Arch doesn't (the package is
cronie), and minimal Fedora/RHEL images may needcronietoo.
Scheduling with a systemd timer
A timer is a pair of units: a .service that says what to run and a .timer that says when. The same nightly backup as above, as a system-wide job:
# /etc/systemd/system/backup-home.service
[Unit]
Description=Back up /home/alice with rsync
[Service]
Type=oneshot
User=alice
ExecStart=/home/alice/bin/backup-home.sh
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/backup-home.timer
[Unit]
Description=Nightly home backup
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup-home.timer
# When does it run next, and when did it last run?
systemctl list-timers backup-home.timer
# Run it once right now, and read its output
sudo systemctl start backup-home.service
journalctl -u backup-home.service -n 50
What you gain over cron:
Persistent=trueruns a missed job at the next boot if the machine was off at the scheduled time.- Everything is logged in the journal with exit status, so
systemctl status backup-home.servicetells you whether last night's run failed. RandomizedDelaySecspreads jobs out, so a fleet of machines doesn't hit the backup server at the same second.- Resource controls (
Nice=,IOSchedulingClass=,MemoryMax=,CPUQuota=) and sandboxing options apply to the job. - No overlap: if the previous run is still going, the timer doesn't start a second copy of the service.
OnCalendar syntax
The format is DayOfWeek Year-Month-Day Hour:Minute:Second, with * as a wildcard and shortcuts such as hourly, daily, weekly and monthly. Check any expression before using it:
systemd-analyze calendar "Mon..Fri *-*-* 09:00"
systemd-analyze calendar "*:0/15" # every 15 minutes
systemd-analyze calendar "Sun *-*-1..7 03:00" # first Sunday of the month
For "every N minutes after the last run" rather than wall-clock times, use OnBootSec= and OnUnitActiveSec= instead of OnCalendar=.
User timers (no root needed)
Put the same two files in ~/.config/systemd/user/, drop the User= line, and manage them with systemctl --user. User timers only run while you are logged in, unless you enable lingering:
systemctl --user daemon-reload
systemctl --user enable --now backup-home.timer
sudo loginctl enable-linger "$USER" # keep user timers running after logout
One-off and quick timers
systemd-run creates a transient timer without writing any files, which is handy for "run this once in an hour":
systemd-run --user --on-active=1h /home/alice/bin/remind.sh
systemd-run --on-calendar="*-*-* 23:00" /usr/local/bin/cleanup.sh # as root
Which one to use
| cron | systemd timer | |
|---|---|---|
| Setup | One line | Two unit files |
| Logs | Only what you redirect | Journal, with exit status |
| Machine was off | Run is skipped (without anacron) | Persistent=true catches up |
| Overlapping runs | Possible | Prevented |
| Resource limits | Manual (nice, ionice) | Built in |
| Portability | Any Unix-like system | systemd-based Linux only |
Related reading
- systemd basics — units,
systemctlandjournalctl - rsync home backup script — a good first job to schedule
- SSL certificate expiry checker — another natural daily timer
- Essential Linux commands