Linux Cron and systemd Timers: A Practical Comparison
Run scheduled jobs on Linux with cron or systemd timers. How they differ, when to choose each, and recipes that survive reboots and log rotations.
What you'll learn
- ✓Cron syntax and gotchas
- ✓How systemd timers are wired
- ✓OnCalendar vs OnBootSec
- ✓Logging and reliability differences
- ✓When to use each tool
Prerequisites
- •Familiar with terminals and YAML
What and Why
Every Linux box ends up running scheduled jobs - backups, rotations, health checks, certificate renewals. For decades the answer was cron. Modern systemd distributions add timers, which are units that schedule other units. Both work; the differences matter once jobs become important.
Choosing well affects observability, reliability, and how easy it is to debug a missed run at 3 a.m. Cron is everywhere and concise; timers are integrated with the rest of your service supervision and give you better logs and dependency handling.
Mental Model
cron:
/etc/crontab + /etc/cron.d/* + per-user crontabs
-> cron daemon forks shell, runs command
-> output mailed via local MTA (often dropped)
systemd timer:
backup.timer (when to fire)
|
v activates
backup.service (what to do, full unit features)
|
v
journald (structured logs, retention) A systemd timer is just a scheduling trigger. The real work lives in a .service unit, so you get all the systemd goodies: environment files, user/group isolation, resource limits, restart policies, and logs in journalctl.
Hands-on Example
The classic cron job:
# /etc/cron.d/backup
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=ops@example.com
15 2 * * * deploy /opt/backup/run.sh >> /var/log/backup.log 2>&1
Five-field schedule: minute, hour, day-of-month, month, day-of-week. Runs 2:15 AM as user deploy.
The systemd equivalent. First the service:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly database backup
After=network-online.target
[Service]
Type=oneshot
User=deploy
EnvironmentFile=/etc/backup.env
ExecStart=/opt/backup/run.sh
Nice=10
IOSchedulingClass=best-effort
Then the timer:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup nightly
[Timer]
OnCalendar=*-*-* 02:15:00
RandomizedDelaySec=15m
Persistent=true
Unit=backup.service
[Install]
WantedBy=timers.target
Enable and inspect:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers --all
journalctl -u backup.service --since "yesterday"
Persistent=true means if the box was off at 02:15, the job runs at next boot. RandomizedDelaySec smears load across machines in a fleet so they do not stampede a shared backend.
Common Pitfalls
- Cron PATH surprises. Cron starts with a minimal PATH. A command that works in your shell may not be found. Set
PATHexplicitly at the top of the crontab. - Silent failures. Cron emails errors to local mail, which is usually unread. Always redirect output to a logfile or pipe through
logger. - Overlapping runs. A long job can stack on top of itself. Use
flockin cron (flock -n /var/lock/backup.lock /opt/backup/run.sh) or rely on systemd’sType=oneshotplus aRemainAfterExit=no. - Timezones in containers. A cron in UTC and a job assuming local time produces midnight bugs. Set
CRON_TZ=in the file. - Forgetting
daemon-reload. After editing a unit, systemd does not see changes until you reload.
Production Tips
- Prefer systemd timers for anything you want to monitor.
journalctl -u name.serviceis far better than greping log files. - Use
OnCalendar=withsystemd-analyze calendar "Mon *-*-* 03:00"to validate expressions before deploying. - For high-frequency or distributed jobs, schedule with Kubernetes CronJobs or a workflow engine (Airflow, Temporal). Cron and timers are best for host-local maintenance.
- Add
OnFailure=alert@%n.serviceso failed jobs page through a unit that wakes Alertmanager or PagerDuty. - Keep both cron and timer definitions in configuration management (Ansible, Chef) so a fresh host is one role away from being scheduled.
Wrap-up
Cron is fine for small, predictable tasks where you do not care much about logs. Systemd timers are the better default on modern Linux: they integrate with service supervision, give you structured logs, and let you express resource limits, jitter, and dependencies. Use timers when reliability matters and reach for cron when brevity does.
Related articles
- Linux Linux Logging with journalctl: Filters, Fields, and Forwarding
Master systemd's journal: query logs with structured filters, tail in real time, persist across reboots, and forward to a central collector.
- Linux systemd Service Units: A Practical Tutorial
Write, install, and operate systemd service units the right way. Learn unit syntax, restart policies, logging with journalctl, and common gotchas.
- Linux Linux Cron Jobs and systemd Timers
Master scheduled tasks on Linux. Learn crontab syntax, fix common PATH and environment pitfalls, and use systemd timers as a modern alternative.
- Linux systemd Basics: Services, Units, and journalctl
Learn systemd from the ground up: units and services, systemctl commands, restart policies, logs with journalctl, and writing your first .service file.