Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 9 min read

Scheduling Scripts: systemd Timers vs Cron, and When to Use Each

cron is everywhere but logs nowhere. Here's a practical comparison of systemd timers and cron for scheduling automation scripts, with config examples for both.

  • #bash
  • #python
  • #systemd
  • #cron
  • #scheduling
  • #automation

Every team has a script that “runs from cron” and a vague memory of where the crontab lives. It works until the day it silently stops, and nobody notices for a week because cron does not log, does not retry, and emails its errors to a mailbox no human has opened since 2015.

In 25 years of scheduling automation I have moved most of my recurring jobs from cron to systemd timers, and I keep cron for the few cases where it genuinely fits. Here is how to decide and how to configure each.

What cron is good at

Cron’s strength is that it is everywhere and dead simple. For a quick periodic job on a single box where you do not care much about observability, it is hard to beat:

# m h  dom mon dow  command
*/15 *  *   *   *   /opt/scripts/sync.sh >> /var/log/sync.log 2>&1

That redirect at the end is mandatory and the thing everyone forgets. Without >> log 2>&1, cron’s output vanishes or gets mailed into the void. Cron has no built-in logging, so you bolt it on manually.

Use cron when: the job is simple, the host has cron, you do not need retries or dependency ordering, and you are fine managing logging yourself.

What systemd timers add

A systemd timer is two files — a service and a timer — and in exchange you get logging, status, dependencies, resource limits, and randomized delays for free.

The service defines what runs:

# /etc/systemd/system/sync.service
[Unit]
Description=Sync job
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/scripts/sync.sh
User=automation

The timer defines when:

# /etc/systemd/system/sync.timer
[Unit]
Description=Run sync every 15 minutes

[Timer]
OnCalendar=*:0/15
Persistent=true
RandomizedDelaySec=60

[Install]
WantedBy=timers.target

Enable it with systemctl enable --now sync.timer. Now you get things cron simply does not have.

Logs you can actually read

Everything the script writes goes to the journal, queryable per-unit:

journalctl -u sync.service --since "1 hour ago"
systemctl status sync.timer        # last run, next run, result
systemctl list-timers              # every timer, sorted by next fire

systemctl list-timers answering “what is scheduled and when does it next run” is worth the switch on its own. With cron you are reading crontabs by hand across the fleet.

Missed runs and jitter

Persistent=true means if the machine was off when the timer should have fired, it runs on next boot — cron just skips missed runs entirely. RandomizedDelaySec=60 spreads the job across a window so a hundred machines do not all hit your backend at exactly *:00. That is the thundering-herd protection you would otherwise build by hand.

Dependencies and limits

After=network-online.target waits for the network before running — no more “failed because DNS was not up yet at boot.” And you can sandbox the job with MemoryMax=, CPUQuota=, PrivateTmp=true, and friends. Cron offers none of this.

OnCalendar beats cron syntax

systemd’s OnCalendar is more readable than the five-field cron incantation, and you can verify it before deploying:

OnCalendar=daily                    # midnight
OnCalendar=Mon..Fri 09:00           # weekdays at 9am
OnCalendar=*-*-01 03:00             # 1st of every month at 3am
OnCalendar=*:0/10                   # every 10 minutes
systemd-analyze calendar "Mon..Fri 09:00"   # shows the next fire times

That systemd-analyze calendar check has saved me from many “I thought it ran on weekdays” mistakes. There is no equivalent dry-run for cron syntax.

A quick decision guide

Reach for cron when: single host, simple job, no retry or dependency needs, and you accept manual logging. It is fine for a personal box or a throwaway.

Reach for systemd timers when: you want logs in the journal, you need the job to wait on the network or another service, you want missed-run recovery, you want jitter across a fleet, or you want resource sandboxing. Which is to say, most production work.

The one caveat: systemd timers assume systemd. Inside many containers, in cron-only environments, or on non-systemd systems, plain cron (or the container orchestrator’s own scheduler) is still the right answer.

Where AI helps

Translating between the two and getting OnCalendar right is fiddly, and AI handles it well. I describe the schedule in English:

“Write a systemd service and timer that runs /opt/scripts/backup.sh as user ‘automation’ every day at 2:30am, waits for the network, recovers missed runs after downtime, and adds up to 5 minutes of random delay. Then give me the equivalent cron line with logging.”

The model gets the OnCalendar syntax and the Persistent/RandomizedDelaySec flags right, and I verify with systemd-analyze calendar before deploying. I keep these scheduling prompts in my prompt library.

The bottom line

Cron is the right tool for simple jobs on a single box. For anything you need to observe — which is most scheduled automation in production — systemd timers give you logging, recovery, jitter, and sandboxing that cron never will. The two extra config files pay for themselves the first time you run journalctl -u instead of guessing why the job stopped.

For more on running automation reliably, see our bash and Python automation guides.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.