Skip to content
C Codeloom
Linux

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.

·4 min read · By Codeloom
Intermediate 10 min read

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)
Two scheduling models

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 PATH explicitly 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 flock in cron (flock -n /var/lock/backup.lock /opt/backup/run.sh) or rely on systemd’s Type=oneshot plus a RemainAfterExit=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.service is far better than greping log files.
  • Use OnCalendar= with systemd-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.service so 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.