Full comparison with real examples — when to use crontab, when to use systemd timers, how to convert between them, and a side-by-side syntax reference for common scheduling patterns.
| Use crontab when… | Use systemd timers when… |
|---|---|
| You need quick setup with minimal files | You need missed runs to fire after reboot (Persistent=true) |
| The script runs on multiple Unix/Linux systems | You want logging via journalctl without extra config |
| You're working in a container or non-systemd system | The job should only run after another service is ready |
| You just need a per-user job without root | You need precise resource limits (CPU, memory) on the job |
| The team is more familiar with cron | You want to test the schedule with systemd-analyze calendar |
| Feature | crontab | systemd timer |
|---|---|---|
| Setup complexity | Low — one file | Medium — two files (.timer + .service) |
| Logging | Email or log file (manual setup) | Automatic via journalctl |
| Missed run recovery | ❌ No (job skipped if machine was off) | ✅ Yes (Persistent=true) |
| Dependency management | ❌ None | ✅ After=network.target, etc. |
| Per-user jobs (no root) | ✅ Yes (crontab -e) | ✅ Yes (user systemd units) |
| Resource limits | ❌ None | ✅ CPUQuota, MemoryMax, etc. |
| Randomized delay | ❌ Not built-in | ✅ RandomizedDelaySec |
| Dry-run / test | Manual (run script, check date) | systemd-analyze calendar "..." |
| Portability | ✅ Any Unix/Linux | systemd-based Linux only |
| Container support | ✅ Works in containers | ❌ Not for containers (no systemd in containers) |
Systemd timers use OnCalendar= in the [Timer] section. The syntax is different but more readable than cron's 5-field format.
| Schedule | Cron expression | Systemd OnCalendar |
|---|---|---|
| Every minute | * * * * * | *:*:00 |
| Every 5 minutes | */5 * * * * | *:0/5:00 |
| Every hour | 0 * * * * | hourly or *:00:00 |
| Every day at midnight | 0 0 * * * | daily or *-*-* 00:00:00 |
| Every day at 2:30 AM | 30 2 * * * | *-*-* 02:30:00 |
| Weekdays at 9 AM | 0 9 * * 1-5 | Mon..Fri *-*-* 09:00:00 |
| Every Monday at 8 AM | 0 8 * * 1 | Mon *-*-* 08:00:00 |
| 1st of month at midnight | 0 0 1 * * | *-*-01 00:00:00 |
| Every week (Sunday) | 0 0 * * 0 | weekly or Sun *-*-* 00:00:00 |
| On boot | @reboot | OnBootSec=0 (different section) |
# /etc/cron.d/backup-job or crontab -e SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin MAILTO="" # Daily database backup at 2 AM 0 2 * * * root /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
A systemd timer requires two files: a .service unit (what to run) and a .timer unit (when to run it).
/etc/systemd/system/db-backup.service[Unit] Description=Daily Database Backup After=network.target [Service] Type=oneshot User=root ExecStart=/usr/local/bin/db-backup.sh StandardOutput=journal StandardError=journal
/etc/systemd/system/db-backup.timer[Unit] Description=Run db-backup daily at 2 AM [Timer] OnCalendar=*-*-* 02:00:00 Persistent=true # Run after reboot if missed RandomizedDelaySec=300 # Add up to 5min random delay [Install] WantedBy=timers.target
sudo systemctl daemon-reload sudo systemctl enable db-backup.timer sudo systemctl start db-backup.timer # Check status systemctl status db-backup.timer systemctl list-timers --all
# All output from the job — no log file setup needed journalctl -u db-backup.service # Follow logs in real time journalctl -u db-backup.service -f # Logs from the last run only journalctl -u db-backup.service --since "1 day ago"
This is one of systemd timers' biggest advantages over cron. With Persistent=true, systemd records the last time the timer fired. If the system was powered off during a scheduled run, the timer fires once immediately on the next boot.
[Timer] OnCalendar=daily Persistent=true # Fire on boot if last run was missed
This is critical for jobs like nightly backups on machines that aren't always on (laptops, development servers). With cron, if the machine is off at 2 AM, the backup simply never runs. With a persistent systemd timer, it runs as soon as you boot the next morning.
Systemd ships with a built-in tool to validate calendar expressions and preview the next run times:
# Test a calendar expression systemd-analyze calendar "Mon..Fri *-*-* 09:00:00" # Output: # Original form: Mon..Fri *-*-* 09:00:00 # Normalized form: Mon..Fri *-*-* 09:00:00 # Next elapse: Mon 2026-05-04 09:00:00 IST # (in UTC): Mon 2026-05-04 03:30:00 UTC # From now: 3 days 21h left # Test @shortcuts systemd-analyze calendar daily systemd-analyze calendar weekly
systemd-analyze calendar is the systemd equivalent of a cron tester. Use it before enabling a timer to confirm it fires when you expect.
If you are running a modern Linux distribution (Ubuntu 20.04+, Debian 11+, RHEL 8+) and want to migrate an existing crontab to systemd, here is the recommended workflow:
Run crontab -l and cat /etc/cron.d/* to list all scheduled jobs. Group them by owner and frequency. Jobs that need dependency management, restart policies, or logging to the journal are the best candidates to migrate first.
Use systemd-analyze calendar "Mon *-*-* 04:00:00" to validate the OnCalendar value before writing the timer unit. The --iterations=5 flag shows the next 5 matching times — equivalent to using CronRead's next-run-times feature for cron expressions. See the conversion table above for cron-to-OnCalendar mappings.
If the job must not be missed (database backups, billing jobs, compliance reports), set Persistent=true in the timer unit. This instructs systemd to trigger the service immediately on boot if the last scheduled run was missed — for example, because the server was down. Standard cron silently skips missed runs with no recovery.
After enabling the timer with systemctl enable --now my-job.timer, verify it with systemctl list-timers --all to confirm the next trigger time. Use journalctl -u my-job.service -f to follow live output. Systemd automatically logs start time, exit code, and duration to the journal — no manual log redirection needed.
Systemd timers are more powerful, but crontab is still the right choice in several common situations:
CMD crond -f entrypoint or a dedicated scheduler like supercronic. For Kubernetes, use a native CronJob resource instead of crontab or systemd.systemctl --user) and loginctl linger to run without an active session.crontab -e takes 10 seconds. Creating a service unit + timer unit + enabling it takes 2–3 minutes. For a simple log-rotation or cache-clear job, crontab's simplicity wins.~/.config/systemd/user/ and are managed with systemctl --user. System-level timers (for all users, run as root or a service account) go in /etc/systemd/system/ and require root.systemctl list-timers to see all active timers and their next fire times. Add --all to include inactive timers.| Guide | What you'll find |
|---|---|
| Linux Crontab Examples | 30+ crontab examples, @shortcuts, output logging |
| Docker Cron Job Examples | supercronic, ofelia, Dockerfile cron patterns |
| Kubernetes CronJob Examples | K8s CronJob YAML, concurrencyPolicy, history |
| Cron Expression Tester | Validate any cron expression online for free |
| Cron Syntax Guide | Full reference for all cron fields and operators |