Scheduler comparison
Cron vs systemd Timers
Which should you use?
Both schedule jobs. They differ in logging, dependencies, and what happens when things go wrong. Here's an honest comparison.
Side-by-side comparison
| Feature | cron | systemd timers |
|---|---|---|
| Configuration | Single line in crontab | Two files (.timer + .service) |
| Syntax | 5-field cron expression | OnCalendar= with calendar events |
| Logging | Syslog (one line per invocation) | journalctl (full stdout/stderr) |
| Dependencies | None — runs independently | Can depend on other units (After=, Requires=) |
| Randomized delay | Not built in | RandomizedDelaySec= built in |
| Missed runs | Skipped silently | Persistent=true catches up |
| Precision | 1-minute minimum | Sub-second with OnActiveSec= |
| Portability | Every Unix/Linux/macOS | Linux with systemd only |
| User jobs | crontab -e (per user) | systemctl --user (per user) |
| Environment | Minimal PATH, set vars manually | Environment= directive in unit file |
| Failure notification | Email via MAILTO (if MTA configured) | OnFailure= triggers another unit |
| Resource limits | None | MemoryMax=, CPUQuota= via cgroups |
When to use cron
- You need something simple that works everywhere — Linux, macOS, BSD, containers
- One-line schedule, no config files to manage
- The job has no dependency on other services starting first
- Your team already knows cron syntax
- You’re in a container or minimal environment without systemd
Cron has survived 40+ years because it does one thing well: run a command on a schedule. If that's all you need, cron is the right choice.
When to use systemd timers
- Your job needs to run after another service starts (dependency ordering with After=)
- You want full stdout/stderr captured automatically in journalctl
- You need randomized delay to avoid thundering-herd problems (RandomizedDelaySec=)
- You want missed runs to catch up after reboot (Persistent=true)
- You need resource limits (MemoryMax=, CPUQuota=) enforced via cgroups
- You need sub-second precision or monotonic timers (OnActiveSec=)
systemd timers are more powerful but also more complex. You need two files instead of one line, and they only work on Linux distributions with systemd.
The real question isn't which scheduler
Both cron and systemd timers share the same blind spot: neither one alerts you when a job doesn't run. Cron silently skips missed runs. systemd's Persistent=true catches up after reboot, but if the timer unit is disabled or the service fails repeatedly, you find out when someone notices the downstream impact.
The scheduler is the easy part. Knowing that your jobs are actually running, completing successfully, and finishing in a reasonable time — that's the hard part.
CronDoctor works with both
Add the signal pattern to either scheduler. CronDoctor detects missed runs, captures error output, and diagnoses failures — regardless of how you schedule the job.
# Nightly backup with CronDoctor monitoring
0 3 * * * curl -sf https://crondoctor.com/api/v1/ping/YOUR_ID/start && \
/opt/scripts/backup.sh 2>/tmp/backup.err && \
curl -sf https://crondoctor.com/api/v1/ping/YOUR_ID/end || \
curl -sf -X POST https://crondoctor.com/api/v1/ping/YOUR_ID/fail \
-H "Content-Type: application/json" \
-d "{\"exit_code\":$?,\"stderr\":\"$(tail -5 /tmp/backup.err)\"}"# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStartPre=curl -sf https://crondoctor.com/api/v1/ping/YOUR_ID/start
ExecStart=/opt/scripts/backup.sh
ExecStartPost=curl -sf https://crondoctor.com/api/v1/ping/YOUR_ID/end# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup nightly
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetAlso works with Kubernetes CronJobs, AWS EventBridge, and any scheduler that can make an HTTP request. See all integration examples →
Try it with your scheduler
CronDoctor monitors your jobs regardless of how you schedule them. One curl command to set up.