systemd Timers: Replace Cron Jobs with a Better Alternative

✅ Tested on Ubuntu 26.04, Debian 12, and Arch Linux — Last updated: June 2026
systemd timers are the modern replacement for cron jobs. A systemd timer is a unit file that triggers another systemd service on a schedule or after an event. Compared to cron, timers have logging (via journald), dependency management, the ability to catch up missed runs, and proper systemd integration for everything you already manage with systemctl. This guide covers creating timers, all scheduling options, converting cron jobs to timers, monotonic vs realtime triggers, and managing timers with systemctl.
- What Are systemd Timers
- List Active Timers
- Create Your First Timer
- Calendar Time Syntax
- Monotonic Timers
- Realtime (Calendar) Timers
- Timer Options Reference
- Manage Timers with systemctl
- Logging and Debugging
- Converting Cron Jobs to Timers
- Practical Examples
- Timers vs Cron Comparison
- Frequently Asked Questions
What Are systemd Timers
Every systemd timer works in a pair: a .timer unit that defines the schedule, and a .service unit that defines the command to run. The timer activates the service.
# A timer pair lives in /etc/systemd/system/:
# mybackup.timer ← defines WHEN to run
# mybackup.service ← defines WHAT to run
# Timers can be:
# - System-wide: /etc/systemd/system/*.timer (run as root or specified user)
# - User-specific: ~/.config/systemd/user/*.timer (run as your user)
# Unlike cron, timers:
# - Log to journald automatically
# - Handle missed runs (if system was off)
# - Have dependencies (wait for network, mount, etc.)
# - Support randomized delay to spread load
# - Are managed with familiar systemctl commandsList Active Timers
# List all active timers:
systemctl list-timers
# NEXT LEFT LAST PASSED UNIT
# Mon 2026-06-08 00:00:00 UTC 3h 45min left Sun 2026-06-07 00:00:00 UTC 20h ago logrotate.timer
# Mon 2026-06-08 02:00:00 UTC 5h 45min left Sun 2026-06-07 02:00:10 UTC 18h ago apt-daily.timer
# List ALL timers (including inactive):
systemctl list-timers --all
# Show only user timers:
systemctl --user list-timersCreate Your First Timer
Here's a complete example: a daily backup script that runs at 2:00 AM every night.
# Step 1: Create the script to run:
sudo nano /usr/local/bin/daily-backup.sh
#!/bin/bash
rsync -av /home/ /backup/home/
# (Make it executable)
sudo chmod +x /usr/local/bin/daily-backup.sh
# Step 2: Create the service unit:
sudo nano /etc/systemd/system/daily-backup.service[Unit]
Description=Daily Home Directory Backup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/daily-backup.sh
User=root
# Optional: send email on failure
# OnFailure=notify-email@%n.service# Step 3: Create the timer unit:
sudo nano /etc/systemd/system/daily-backup.timer[Unit]
Description=Run daily backup at 02:00
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target# Step 4: Enable and start the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now daily-backup.timer
# Verify it's scheduled:
systemctl status daily-backup.timer
systemctl list-timers daily-backup.timerCalendar Time Syntax
The OnCalendar= directive uses a special syntax. The full format is: DayOfWeek Year-Month-Day Hour:Minute:Second. Asterisks are wildcards, commas separate values, and ranges use ...
# Test calendar expressions before using:
systemd-analyze calendar "Mon *-*-* 09:00:00"
# Output shows:
# Next elapse: Mon 2026-06-10 09:00:00 UTC
# From now: 1 day 22h left
systemd-analyze calendar "daily"
systemd-analyze calendar "weekly"
systemd-analyze calendar "0/2:00:00" # every 2 hours| Expression | Meaning |
|---|---|
hourly | Every hour (at :00:00) |
daily | Every day at 00:00:00 |
weekly | Every Monday at 00:00:00 |
monthly | First of every month at 00:00:00 |
annually | January 1st at 00:00:00 |
*-*-* 02:30:00 | Every day at 2:30 AM |
Mon *-*-* 09:00:00 | Every Monday at 9:00 AM |
*-*-1 00:00:00 | First day of every month |
*-*-1,15 00:00:00 | 1st and 15th of every month |
Mon-Fri *-*-* 08:30:00 | Weekdays at 8:30 AM |
*:0/15 | Every 15 minutes |
0/6:00:00 | Every 6 hours (00:00, 06:00, 12:00, 18:00) |
*-*-* *:00:00 | Every hour on the hour (same as hourly) |
2026-*-* 12:00:00 | Every day during 2026 at noon |
Monotonic Timers
Monotonic timers trigger relative to an event (system boot, service start) rather than a specific clock time. They're ideal for "run X minutes after boot" or "run every Y hours while system is up."
# Monotonic timer directives:
[Timer]
# Run 10 minutes after the system boots:
OnBootSec=10min
# Run 5 minutes after the timer unit itself is started:
OnActiveSec=5min
# Run 1 hour after the last time this service completed:
OnUnitActiveSec=1h
# Run 30 minutes after the last time this service was started:
OnUnitInactiveSec=30min
# Multiple triggers (run 5min after boot AND every 4 hours after that):
OnBootSec=5min
OnUnitActiveSec=4h
# Time units: us, ms, s (seconds), min, h, d, w, month, year
# Examples:
# 30s = 30 seconds
# 5min 30s = 5 minutes and 30 seconds
# 1h 30min = 1.5 hoursRealtime (Calendar) Timers
# Key options for realtime timers:
[Timer]
# The schedule (uses calendar syntax):
OnCalendar=*-*-* 03:00:00
# Persistent: if the timer was missed (system off), run immediately on next boot:
Persistent=true
# Randomize start by up to 1 hour to spread load across multiple servers:
RandomizedDelaySec=1h
# Accuracy: how precisely to honor the time (default: 1min)
# Decreasing this wakes CPU more often, use for time-sensitive tasks:
AccuracySec=1s # wake up exactly at scheduled time
AccuracySec=1h # anywhere within 1 hour (default is 1min)Timer Options Reference
| Option | Description |
|---|---|
OnCalendar= | Calendar-based schedule (realtime clock) |
OnBootSec= | Seconds after system boot |
OnActiveSec= | Seconds after timer activation |
OnUnitActiveSec= | Seconds after service last ran |
Persistent=true | Run immediately if last trigger was missed |
RandomizedDelaySec= | Random delay up to this value (spread load) |
AccuracySec= | Timer precision (default: 1min) |
Unit= | Service to activate (default: same name as timer) |
WakeSystem=true | Wake system from sleep to run timer |
Manage Timers with systemctl
# Enable timer (start on boot):
sudo systemctl enable daily-backup.timer
# Start timer now (without enabling):
sudo systemctl start daily-backup.timer
# Enable and start in one command:
sudo systemctl enable --now daily-backup.timer
# Stop the timer (no more scheduled runs):
sudo systemctl stop daily-backup.timer
# Disable (won't start on boot):
sudo systemctl disable daily-backup.timer
# Run the service right now (bypasses timer):
sudo systemctl start daily-backup.service
# Check timer status and next run time:
systemctl status daily-backup.timer
# Reload after editing unit files:
sudo systemctl daemon-reload
# User timers (run without sudo):
systemctl --user enable --now my-timer.timer
systemctl --user list-timersLogging and Debugging
# View logs for the service (not the timer):
journalctl -u daily-backup.service
# Follow logs in real time:
journalctl -u daily-backup.service -f
# Logs from last run only:
journalctl -u daily-backup.service -n 50
# Logs since yesterday:
journalctl -u daily-backup.service --since yesterday
# Check if last run succeeded (exit code):
systemctl show daily-backup.service --property=ExecMainStatus
# ExecMainStatus=0 means success
# See when timer last triggered and when it triggers next:
systemctl show daily-backup.timer --property=LastTriggerUSec
systemctl show daily-backup.timer --property=NextElapseUSecRealtime
# Detailed timer status:
systemctl status daily-backup.timer
# ● daily-backup.timer - Run daily backup at 02:00
# Loaded: loaded (/etc/systemd/system/daily-backup.timer; enabled)
# Active: active (waiting) since Mon 2026-06-08
# Trigger: Tue 2026-06-09 02:00:00 UTC; 21h left
# Trig. by: daily-backup.service
# Triggers: ● daily-backup.serviceConverting Cron Jobs to Timers
Here's a translation guide for common cron expressions:
| Cron expression | systemd OnCalendar= |
|---|---|
@hourly | hourly |
@daily | daily |
@weekly | weekly |
@monthly | monthly |
@reboot | Use OnBootSec= |
0 2 * * * | *-*-* 02:00:00 |
30 8 * * 1 | Mon *-*-* 08:30:00 |
0 0 1 * * | *-*-1 00:00:00 |
*/15 * * * * | *:0/15 |
0 */6 * * * | 0/6:00:00 |
0 8 * * 1-5 | Mon-Fri *-*-* 08:00:00 |
# Converting: cron job that runs a script as www-data at 3 AM daily:
# Cron: 0 3 * * * www-data /usr/local/bin/cleanup.sh
# Service file:
# /etc/systemd/system/cleanup.service
[Unit]
Description=Daily Cleanup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
User=www-data
# Timer file:
# /etc/systemd/system/cleanup.timer
[Unit]
Description=Run daily cleanup at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetPractical Examples
# Example 1: Run a Python script every 15 minutes# /etc/systemd/system/data-collector.service[Unit]Description=Data Collection Script[Service]Type=oneshotExecStart=/usr/bin/python3 /opt/collector/run.pyUser=collectorWorkingDirectory=/opt/collector# /etc/systemd/system/data-collector.timer[Unit]Description=Run data collector every 15 minutes[Timer]OnCalendar=*:0/15RandomizedDelaySec=60 # random 0-60s delay so not all servers hit API at once[Install]WantedBy=timers.target# Example 2: Disk usage report sent to email (Monday mornings)
# /etc/systemd/system/disk-report.service
[Unit]
Description=Weekly Disk Usage Report
[Service]
Type=oneshot
ExecStart=/bin/bash -c "df -h | mail -s 'Weekly Disk Report' admin@example.com"
User=root
# /etc/systemd/system/disk-report.timer
[Unit]
Description=Weekly disk report on Monday
[Timer]
OnCalendar=Mon *-*-* 07:00:00
Persistent=false # if missed, skip it (not critical)
[Install]
WantedBy=timers.target