systemd linux operations

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.service shows timestamped, persistent, filterable output for every run. With cron, output goes to email or syslog — often misconfigured, often lost.
  • Persistent scheduling: Persistent=true in 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.target ensures 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=10min spreads 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.sh
  • User=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:00
  • hourly — shorthand for *-*-* *:00:00
  • Mon..Fri 09:00 — weekdays at 9 AM
  • *-*-* 02:00:00 — daily at 2 AM
  • *-*-1 00:00:00 — first day of every month at midnight
  • Sat,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.service with ExecStart=/usr/local/bin/backup.sh
  • Create /etc/systemd/system/backup.timer with OnCalendar=*-*-* 02:00:00 and Persistent=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.

Related Guides