Blog
4 min read

systemd Timers vs cron: Scheduling Jobs on a Linux Server

cron is simple and everywhere; systemd timers give you logging, missed-run catch-up, randomized delays, resource limits and dependencies. A side-by-side comparison, a complete timer and service example with OnCalendar, how to test calendar expressions, list and debug timers, and which to choose.

Every server needs scheduled jobs: nightly backups, cleanup scripts, sending a daily digest. On Linux you have two built-in options: classic cron and systemd timers. (Background Jobs and Cron)

Side by side

cron systemd timer
Setup One line in a crontab Two small files (.timer + .service)
Logs Mailed or lost unless you redirect them In the journal automatically (journalctl)
Missed runs (server was off) Skipped Can catch up with Persistent=true
Overlapping runs Possible No — a running service isn't started twice
Random delay (avoid thundering herd) Manual sleep RandomizedDelaySec=
Resource limits, sandboxing No Yes — all systemd service options
Dependencies ("after the network is up") No Yes
Run it manually, exactly as scheduled Copy the command systemctl start job.service

cron wins on simplicity. Timers win on everything you'll want once a job matters.

A cron job

crontab -e
17 3 * * * /home/app/bin/backup.sh >> /var/log/backup.log 2>&1

Remember cron runs with a minimal environment — not your login shell's PATH or variables — which is the cause of most "works in my terminal, fails in cron" problems. (Cron Expressions Explained, PATH and Command Not Found)

The same job as a systemd timer

The service — what to run:

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly database backup

[Service]
Type=oneshot
User=app
ExecStart=/home/app/bin/backup.sh
# Optional hardening and limits
Nice=10
MemoryMax=512M

The timer — when to run it:

# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup nightly

[Timer]
OnCalendar=*-*-* 03:17:00
Persistent=true
RandomizedDelaySec=5min

[Install]
WantedBy=timers.target

Enable and start the timer (not the service):

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
  • Persistent=true — if the server was off at 03:17, run as soon as it boots.
  • RandomizedDelaySec — spreads start times so many servers don't hit an API or backup target at the same second.
  • Type=oneshot — the service runs to completion, then stops.

(Writing a systemd Service File, What Is systemd?)

OnCalendar examples

Expression Meaning
hourly Every hour at :00
daily Every day at midnight
Mon *-*-* 09:00 Mondays at 09:00
Mon..Fri 08:30 Weekdays at 08:30
*:0/15 Every 15 minutes
*-*-01 04:00 1st of each month at 04:00

Test an expression before using it:

systemd-analyze calendar "Mon..Fri 08:30"

It prints the normalised form and the next time it will fire. Timers can also run relative to events: OnBootSec=5min, OnUnitActiveSec=1h.

Seeing what's scheduled and what happened

systemctl list-timers               # every timer, last run and next run
systemctl status backup.service     # last result
journalctl -u backup.service -n 50  # output from recent runs
sudo systemctl start backup.service # run it right now, the same way the timer does

That last command is the killer feature: you test the job in exactly the environment it will run in.

Getting alerted on failure

Add OnFailure= to the service's [Unit] section to trigger another unit (for example, one that sends an email or Slack message) whenever the job fails. Or have your script ping a heartbeat monitor on success, so silence means failure. (Know When Your App Is Down)

Which should you use?

  • Quick personal job, or a system without systemd (some containers): cron.
  • Anything production — backups, billing, data syncs: a systemd timer, for the logs, catch-up and no overlaps.
  • Jobs that belong to your app (send this email in 10 minutes, retry on failure): a job queue inside the app. (Postgres SKIP LOCKED Job Queue, BullMQ Tutorial)

The summary

  • cron: one line, minimal features, silent failures.
  • systemd timers: a .timer + .service pair with logging, Persistent=true, random delays and limits.
  • Use systemctl list-timers, journalctl -u, and systemd-analyze calendar to manage them.

EasySpawn gives each app a persistent server where scheduled jobs run next to your database, with daily backups already handled — and Claude Code to write the timers. See how it works or join the waitlist.

Related: Bash Scripting for Beginners · PM2 vs systemd · Run a Python Script 24/7 · Scheduled GitHub Actions

Keep reading