Cron vs systemd timers — when each fits and which to reach for
Linux gives you two ways to schedule recurring work: the 50-year-old cron, and systemd timers. Both work. They have different tradeoffs, and "always use one" advice is wrong — pick per use case. This article walks through the differences so you can decide quickly without re-relitigating it every time.
Cron: simple, ubiquitous, limited
What cron does well:
- Trivially simple syntax for periodic jobs.
0 3 * * * /usr/local/bin/backup.sh— every distro since the 90s reads it. - Per-user crontabs via
crontab -e. The job runs as that user with that user's environment. - Auto-emails on output. If the job writes to stdout/stderr, cron mails it to the user (or whatever
MAILTO=says). Useful for "let me know if this fails" without writing alerting code. - Drop-in files at
/etc/cron.d/for system-level jobs, or hourly/daily/weekly/monthly directories where you drop scripts named00-fooand they run on the matching cadence.
What cron does badly:
- No persistence across downtime. If the VPS was off when 03:00 came, the job doesn't run when the box comes back up.
anacroncan paper over this; on systemd-based distros, timers do it natively. - No native dependency model. Cron jobs run in isolation; you can't say "run job B 30 minutes after job A finishes."
- Output handling is email or nothing. If you want logs, you redirect to a file and roll your own rotation. Cron itself logs only the fact that a job ran, not what it produced.
Systemd timers: more verbose, more capable
A timer is a unit file (foo.timer) that activates a service unit (foo.service) on a schedule. The schedule lives in the timer, the work lives in the service — two files for what cron does in one line.
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true # run on next boot if missed during downtime
RandomizedDelaySec=15m # spread load if many timers fire on same schedule
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers --all
What timers do better than cron:
Persistent=true— missed runs catch up on next boot.RandomizedDelaySec=— jitter the schedule. Stops "every server in the fleet hits the same backup target at exactly 03:00."- Output captured by journald automatically —
journalctl -u backupgives you what the job said. No log-rotation roll-your-own. - Service unit features apply —
Restart=on-failure, sandboxing (NoNewPrivileges,PrivateTmp), environment-file inclusion (EnvironmentFile=/etc/backup.env), etc. - Dependency model — a service can require another to have completed first.
What timers do worse:
- Two files for what cron does in one line. For trivial periodic jobs, the verbosity grates.
- No native email-on-output. You wire it up yourself (an
OnFailure=handler that sends to a notification service).
When to use which
Use cron for:
- Quick one-liner periodic jobs where the script handles its own logging/alerting.
- Cases where you want default mail-on-output behavior.
- Legacy scripts being ported in from another box — preserve the cron syntax, don't rewrite into systemd just because.
- Personal crontabs (
crontab -e) — much lower-friction than user-mode systemd timers.
Use systemd timers for:
- Backup jobs (you want
Persistent=true+ the service-unit features for logging/restart). - Anything that should survive missed runs after a reboot.
- Jobs that benefit from systemd's sandboxing options (running as a service user with restricted filesystem access).
- Jobs that need dependencies on other services being up first (
After=postgresql.service). - Fleet jobs where you want the randomized delay so 50 VPSes don't all hit your backup target at exactly the same second.
OnCalendar syntax cheat sheet
Systemd's calendar format is more flexible than cron's but uses different syntax:
OnCalendar=hourly # every hour at :00
OnCalendar=daily # every day at 00:00
OnCalendar=weekly # Mon 00:00
OnCalendar=*-*-* 03:00:00 # daily at 03:00
OnCalendar=Mon,Wed,Fri 14:30 # specific weekdays at 14:30
OnCalendar=*-*-01 04:00:00 # first of every month at 04:00
OnCalendar=*-*-* *:0/15:00 # every 15 minutes (top of hour, :15, :30, :45)
Validate before deploying:
systemd-analyze calendar "Mon,Wed,Fri 14:30"
# Next elapse: Wed 2026-06-24 14:30:00 UTC
Migration: cron to timer
For each cron entry, the timer-equivalent is:
| Cron | OnCalendar |
|---|---|
0 3 * * * | *-*-* 03:00:00 |
*/15 * * * * | *-*-* *:0/15:00 |
0 0 * * 0 | Sun *-*-* 00:00:00 |
@reboot | use a service with WantedBy=multi-user.target + no timer |
You don't have to migrate everything. Mixed cron + timer setups are normal and fine. Migrate when you have a specific reason (e.g., a backup job that keeps missing runs after reboots).
Also Read
Powered by WHMCompleteSolution