systemd Timers vs Cron: Which Linux Scheduler to Use in 2026?
Cron has been the default choice for scheduled tasks on Linux for decades. But systemd timers offer logging, dependency management, and missed-job recovery that cron simply cannot match. This guide walks through both options with real examples so you can choose the right tool for your infrastructure.
The Problem with Cron in 2026
Cron works. It has worked since 1975, and for simple one-liner jobs it remains perfectly adequate. But on modern Ubuntu and RHEL systems running systemd, cron has a fundamental weakness: there is no native way to see why a job failed, whether it ran at all, or what it output.
Consider this real scenario: a nightly database backup cron job silently fails because /mnt/backup is not mounted yet when the job fires at 02:00. Cron does not know about mounts. It fires the script, the script exits with an error, and unless you have configured email output (which most admins skip), you find out about the failure when the database needs restoring.
systemd timers solve this class of problem. They integrate with the journal for structured logging, support After= and Requires= dependencies, and with Persistent=true can run a missed job as soon as the machine boots. These are not minor conveniences - they are the difference between a reliable scheduled task and a silent failure waiting to happen.
Quick Comparison
| Feature | cron | systemd timer |
|---|---|---|
| Logging | Mail or redirect manually | Built-in via journalctl |
| Missed job recovery | No | Yes (Persistent=true) |
| Dependencies | No | Yes (After=, Requires=) |
| Resource limits | No | CPUQuota, MemoryMax, etc. |
| Per-user timers | Yes (crontab -e) | Yes (--user) |
| Syntax complexity | Low | Higher (two unit files) |
| Randomized delay | No | Yes (RandomizedDelaySec) |
| On-boot delay | No | Yes (OnBootSec) |
How Cron Works
Cron reads entries from /etc/crontab, /etc/cron.d/, and per-user crontabs managed via crontab -e. Each line specifies a schedule in five fields (minute, hour, day, month, weekday) followed by the command to run:
# Run at 02:00 every day as root
0 2 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Run every 15 minutes
*/15 * * * * www-data /usr/bin/php /var/www/cron.php
# Run at 09:00 Monday to Friday
0 9 * * 1-5 deploy /opt/deploy/notify.sh
The five-field syntax is compact but has well known limitations. The smallest interval is one minute. There is no built-in way to run a job on boot, after a delay, or only when a specific service is running. Output goes to email (if configured) or is silently discarded.
How systemd Timers Work
A systemd timer consists of two unit files: a .timer file that defines the schedule, and a .service file that defines what to run. The timer activates the service at the configured time.
Step 1: Create the service unit
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly database backup
After=network.target mysql.service
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal
Step 2: Create the timer unit
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup service daily at 02:00
Requires=backup.service
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
Step 3: Enable and start the timer
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
# Verify it is active
sudo systemctl list-timers --all | grep backup
The Persistent=true directive means: if the machine was off at 02:00, run the job immediately on next boot. RandomizedDelaySec=300 adds a random delay of up to 5 minutes, which is useful when many timers would otherwise fire at the exact same second and overwhelm the system.
Real Example: Log Rotation Timer
This example rotates application logs weekly and limits the service to 256MB of memory, preventing a runaway log rotation from impacting the production workload:
# /etc/systemd/system/logrotate-app.service
[Unit]
Description=Rotate application logs
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.d/myapp
MemoryMax=256M
Nice=15
# /etc/systemd/system/logrotate-app.timer
[Unit]
Description=Weekly log rotation
[Timer]
OnCalendar=weekly
Persistent=true
[Install]
WantedBy=timers.target
Real Example: Monotonic Timer (Run After Boot)
Monotonic timers fire relative to events rather than calendar time. This is useful for tasks that should run some time after boot, not at a fixed clock time:
# /etc/systemd/system/cache-warmup.timer
[Unit]
Description=Warm up cache 5 minutes after boot
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target
OnBootSec=5min fires 5 minutes after boot. OnUnitActiveSec=1h then repeats every hour after the last activation. There is no equivalent in standard cron.
Viewing Logs for a Timer Job
This is where systemd timers genuinely shine over cron. Every run of a timer-activated service is logged to the journal with a timestamp, exit code, and all stdout/stderr output:
# View all output from the last backup run
journalctl -u backup.service -n 50
# Follow output in real time
journalctl -u backup.service -f
# See all timer runs with timestamps
systemctl list-timers backup.timer
# Check the last run status
systemctl status backup.service
With cron, you need to explicitly redirect output to a log file and then remember to check that file. With systemd, the journal captures everything automatically and you can query it with structured filters, time ranges, and priority levels.
Build and Validate Cron Expressions
Not sure your cron schedule is correct? Use our free Crontab Generator to build and preview expressions with a human-readable explanation - no server, no sign-up.
Open Crontab GeneratorOnCalendar Syntax Reference
The systemd OnCalendar syntax is more expressive than cron's five-field format:
OnCalendar=daily # Every day at 00:00
OnCalendar=weekly # Every Monday at 00:00
OnCalendar=monthly # First of each month at 00:00
OnCalendar=hourly # Top of every hour
OnCalendar=*:0/15 # Every 15 minutes
OnCalendar=Mon..Fri 09:00 # Weekdays at 09:00
OnCalendar=*-*-* 02:30:00 # Every day at 02:30
OnCalendar=2026-03-26 12:00 # Specific date and time
You can validate any OnCalendar expression without creating a unit file:
systemd-analyze calendar "Mon..Fri 09:00"
# Output: Next elapse: Mon 2026-03-30 09:00:00 UTC
Per-User Timers
Systemd supports per-user timers that run without root privileges, analogous to crontab -e:
# Place unit files in ~/.config/systemd/user/
# Enable with --user flag
systemctl --user enable --now mytask.timer
systemctl --user list-timers
journalctl --user -u mytask.service
When to Use Cron vs systemd Timers
Stick with cron when:
- The system does not use systemd (Alpine, older embedded Linux, BSD)
- You need to manage schedules for many non-root users without admin access
- The job is a simple one-liner with no dependency requirements
- You are already managing dozens of existing crontabs and migration is not worth the effort
Use systemd timers when:
- You need structured logging and easy failure investigation
- The job depends on a service, mount, or network being available
- The machine may be off at the scheduled time and you need catch-up behavior
- You want resource limits (CPU, memory, I/O) on the scheduled task
- You are writing a new service from scratch on a modern systemd-based distribution
If you are deploying to Ubuntu 20.04+ or RHEL 8+, systemd timers are the idiomatic choice. The tooling, logging, and dependency management are simply better for anything beyond the simplest scheduled command.
Migrating an Existing Crontab to systemd
To migrate a cron job to a systemd timer, the process is straightforward. Take this example cron entry:
30 3 * * * root /opt/scripts/cleanup.sh > /var/log/cleanup.log 2>&1
The equivalent systemd pair removes the output redirect (the journal handles it) and adds dependency support:
# /etc/systemd/system/cleanup.service
[Unit]
Description=Daily cleanup script
After=local-fs.target
[Service]
Type=oneshot
User=root
ExecStart=/opt/scripts/cleanup.sh
# /etc/systemd/system/cleanup.timer
[Unit]
Description=Run cleanup daily at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
Frequently Asked Questions
Can a systemd timer replace all cron jobs?
On systems running systemd (Ubuntu, Debian, Fedora, RHEL, Arch), yes - systemd timers can replace every cron job. The only exception is systems that do not use systemd as their init system, such as Alpine Linux (which uses OpenRC) or FreeBSD.
What happens if a timer fires while the previous instance is still running?
By default, systemd will not start a new instance of a Type=oneshot service if the previous run has not finished. You can override this with ConcurrentStarts=true in the service file if overlap is acceptable. Cron has no such protection - it will start a second instance regardless.
How do I see all active timers on the system?
Run systemctl list-timers --all. This shows every timer, its next scheduled run, its last run, and how long ago it last triggered. There is no equivalent single command in cron that shows all jobs across all users with their next run times.
Does Persistent=true guarantee the missed job runs exactly once?
Yes. Persistent=true records the last activation time on disk. On boot, if the scheduled time has passed since the last run, the timer fires once immediately. It does not attempt to run multiple times to "catch up" on every missed interval - it runs once and then resumes the normal schedule.
Can I set environment variables for a systemd timer job?
Yes. Add an [Service] section with Environment="KEY=value" or use EnvironmentFile=/etc/myapp/env to load from a file. This is cleaner than cron, which inherits a minimal environment and often requires full paths for every binary.
How do I temporarily disable a timer without deleting it?
Run systemctl stop backup.timer to stop it immediately, or systemctl disable backup.timer to prevent it from starting on boot. To re-enable: systemctl enable --now backup.timer. With cron you would need to comment out the line in the crontab file.
Use our free tool here → Crontab Generator to build and validate cron expressions before adding them to your crontab or converting them to systemd OnCalendar format.
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.