Cron vs Systemd Timers: When to Use Each
by Sinthuyan · April 20, 2026
Systemd timers are available on every modern Linux distribution and solve several problems that cron handles poorly: log management, missed-run recovery, dependency ordering, and load distribution. But cron isn't going away — it remains the right tool for some use cases. Here's how to choose.
Why Systemd Timers Exist
Cron was designed in an era before structured logging and service managers. Its output handling (sending to email, falling on the floor if no MTA is configured) and lack of dependency awareness have been pain points for decades. Systemd timers address these directly:
- Structured logging:
journalctl -u yourjob.serviceshows timestamped, persistent, filterable output for every run. With cron, output goes to email or syslog — often misconfigured, often lost. - Persistent scheduling:
Persistent=truein a timer unit means "if the system was off at the scheduled time, run the job once when it comes back online." Cron silently misses runs during downtime. - Dependency management:
After=network-online.targetensures the job doesn't attempt to run before the network is available — a common failure mode for cron jobs that make outbound requests at boot. - Built-in jitter:
RandomizedDelaySec=10minspreads load across a window, equivalent to Jenkins' H symbol but available for any systemd timer.
How Systemd Timers Work
A systemd timer consists of two unit files: a .service file (what to run) and a .timer file (when to run). They must share the same base name.
The service unit (/etc/systemd/system/myjob.service) defines the command:
[Service]ExecStart=/usr/local/bin/myjob.shUser=www-data
The timer unit (/etc/systemd/system/myjob.timer) defines the schedule:
[Timer]OnCalendar=*-*-* 02:00:00(daily at 2 AM)Persistent=true[Install]WantedBy=timers.target
Enable and start: systemctl enable --now myjob.timer. Check status: systemctl list-timers.
OnCalendar Syntax
Systemd's calendar syntax is not cron syntax. It uses ISO 8601-style notation:
daily— shorthand for*-*-* 00:00:00hourly— shorthand for*-*-* *:00:00Mon..Fri 09:00— weekdays at 9 AM*-*-* 02:00:00— daily at 2 AM*-*-1 00:00:00— first day of every month at midnightSat,Sun 03:00— weekends at 3 AM
Use systemd-analyze calendar 'Mon..Fri 09:00' to validate a calendar expression and see the next trigger time.
Migrating a Cron Job
To migrate 0 2 * * * /usr/local/bin/backup.sh to a systemd timer:
- Create
/etc/systemd/system/backup.servicewithExecStart=/usr/local/bin/backup.sh - Create
/etc/systemd/system/backup.timerwithOnCalendar=*-*-* 02:00:00andPersistent=true - Run
systemctl daemon-reload && systemctl enable --now backup.timer - Remove the cron entry
When to Keep Cron
Systemd timers are system-wide by default. User-level crontabs (where individual users schedule their own jobs without root access) have no direct systemd equivalent unless user lingering is enabled. For multi-user systems where non-root users need to schedule jobs, cron remains the right choice.
Cron is also appropriate in environments where systemd isn't available: containers using init-less base images, BSD systems, macOS (which uses launchd), and legacy systems pre-dating systemd. If the infrastructure is already stable and working, the migration cost may not be justified.
systemctl list-timers: Your New Best Friend
Run systemctl list-timers to see every active timer with: next trigger time, last trigger time, elapsed since last run, and the associated service name. This is dramatically more useful than crontab -l, which shows expressions but not execution history.