🕐 Systemd Timers

Cron & Scheduling Series: Part 1 — Cron Deep Dive  |  Part 2 — Systemd Timers  |  Part 3 — at & anacron

Systemd Timers — The Modern Scheduler

Systemd timers are the modern alternative to cron — they integrate fully with systemd, log to journald, support dependency ordering, can catch up on missed jobs, and give you much more flexible scheduling options. Every timer is paired with a service unit that does the actual work, keeping scheduling and execution cleanly separated.

Timers are not a drop-in replacement for cron — they require more setup — but for system-level scheduled tasks on modern RHEL/Ubuntu servers they are the preferred approach. Many system timers ship with packages and are already running on your system right now.

How Timers Work — Two Unit Files

Every timer needs two unit files with the same base name:

mybackup.timer — defines WHEN to run
mybackup.service — defines WHAT to run

When the timer fires it activates the matching .service unit. The service runs the job and exits. The timer waits for the next trigger.

Timer Types

TypeDirectiveDescription
RealtimeOnCalendar=Calendar-based — like cron. Fires at a specific wall-clock time.
MonotonicOnBootSec=, OnActiveSec=, OnUnitActiveSec=Relative to boot or last activation. Not affected by clock changes.
PersistentPersistent=trueCatches up on missed jobs — fires immediately if timer was missed while system was off.

Examples

1
View Existing Timers
# List all active timers with next/last trigger times
systemctl list-timers

# List ALL timers including inactive
systemctl list-timers --all

# Check status of a specific timer
systemctl status dnf-makecache.timer
systemctl status logrotate.timer

# See what timers are installed on the system
find /etc/systemd/system /lib/systemd/system -name "*.timer" | sort
systemctl list-timers output:
NEXT LEFT LAST PASSED UNIT Tue 2026-04-26 00:00:00 EDT 8h left Mon 2026-04-25 00:00:02 EDT 15h ago logrotate.timer Tue 2026-04-26 06:34:00 EDT 14h left Mon 2026-04-25 06:34:12 EDT 9h ago dnf-makecache.timer Tue 2026-04-26 10:00:00 EDT 18h left Mon 2026-04-25 10:00:08 EDT 5h ago fstrim.timer n/a n/a Mon 2026-04-25 02:00:01 EDT 13h ago myjob.timer 4 timers listed.
💡 systemctl list-timers is the first thing to run when diagnosing scheduling issues — it shows exactly when each timer last ran and when it will run next. Far more informative than grepping through cron logs.
2
Create a Simple Daily Timer

A complete example — nightly backup running at 2 AM:

# Step 1: Create the service unit (what to run)
sudo tee /etc/systemd/system/nightly-backup.service << 'EOF'
[Unit]
Description=Nightly Backup Job
After=network.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/nightly-backup.sh
StandardOutput=journal
StandardError=journal
EOF

# Step 2: Create the timer unit (when to run)
sudo tee /etc/systemd/system/nightly-backup.timer << 'EOF'
[Unit]
Description=Run nightly backup at 2 AM
Requires=nightly-backup.service

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

# Step 3: Reload systemd and enable the timer
sudo systemctl daemon-reload
sudo systemctl enable --now nightly-backup.timer

# Step 4: Verify
systemctl list-timers nightly-backup.timer
systemctl status nightly-backup.timer
Type=oneshot: Use Type=oneshot for jobs that run and exit — like cron jobs. The service starts, runs the command, and exits. Systemd considers it successful when the command exits 0.
3
OnCalendar — Calendar Syntax

OnCalendar uses systemd's calendar expression syntax — more powerful than cron's 5-field format:

# Basic calendar expressions
OnCalendar=daily                  # every day at midnight
OnCalendar=weekly                 # every Monday at midnight
OnCalendar=monthly                # first of month at midnight
OnCalendar=hourly                 # every hour on the hour
OnCalendar=minutely               # every minute

# Specific time
OnCalendar=*-*-* 02:30:00        # every day at 2:30 AM
OnCalendar=*-*-* 06,18:00:00     # 6 AM and 6 PM daily
OnCalendar=Mon *-*-* 08:00:00    # every Monday at 8 AM
OnCalendar=Mon-Fri *-*-* 08:00:00 # weekdays at 8 AM

# Specific date
OnCalendar=2026-12-31 23:59:00   # once on New Year's Eve
OnCalendar=*-01-01 00:00:00      # every Jan 1st (yearly)
OnCalendar=*-*-01 00:00:00      # first of every month

# Intervals
OnCalendar=*:0/15                 # every 15 minutes
OnCalendar=*:00/5                 # every 5 minutes

# Verify your calendar expression
systemd-analyze calendar "Mon-Fri *-*-* 08:00:00"
systemd-analyze calendar "*:0/15"
systemd-analyze calendar output:
Original form: Mon-Fri *-*-* 08:00:00 Normalized form: Mon..Fri *-*-* 08:00:00 Next elapse: Tue 2026-04-29 08:00:00 EDT (in UTC): Tue 2026-04-29 12:00:00 UTC From now: 3 days left
💡 Always verify with systemd-analyze calendar. It shows the normalized form, next scheduled run, and time remaining. The equivalent of crontab.guru for systemd timers.
4
Monotonic Timers — Relative to Boot

Monotonic timers fire relative to system events rather than wall-clock time — perfect for tasks that should run after boot or at regular intervals from startup:

# Timer that runs 5 minutes after boot
sudo tee /etc/systemd/system/post-boot-check.timer << 'EOF'
[Unit]
Description=Run post-boot checks 5 minutes after startup

[Timer]
OnBootSec=5min
Unit=post-boot-check.service

[Install]
WantedBy=timers.target
EOF

# Timer that runs 15 minutes after boot, then every hour
sudo tee /etc/systemd/system/hourly-poll.timer << 'EOF'
[Unit]
Description=Poll service every hour starting 15min after boot

[Timer]
OnBootSec=15min
OnUnitActiveSec=1h

[Install]
WantedBy=timers.target
EOF
DirectiveFires when...Example
OnBootSec=N time after system bootOnBootSec=5min
OnStartupSec=N time after systemd startedOnStartupSec=2min
OnActiveSec=N time after timer activatedOnActiveSec=30s
OnUnitActiveSec=N time after service last ranOnUnitActiveSec=1h
OnUnitInactiveSec=N time after service last finishedOnUnitInactiveSec=30min
5
Persistent Timers — Never Miss a Job

Persistent=true is the anacron equivalent for systemd timers — if the system was off when the timer should have fired, it fires immediately on next boot:

# Timer with Persistent=true — fires immediately on boot if missed
sudo tee /etc/systemd/system/weekly-report.timer << 'EOF'
[Unit]
Description=Weekly report — fires even if system was off

[Timer]
OnCalendar=Mon *-*-* 06:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
EOF
Persistent=true + RandomizedDelaySec is the ideal combination for batch jobs on multiple servers. Persistent=true ensures missed jobs run on next boot. RandomizedDelaySec=300 adds a random 0-300 second delay — so 50 servers don't all hammer a shared resource at exactly the same time.
# Check when a timer last ran and whether it was missed
systemctl show nightly-backup.timer | grep -E "LastTrigger|NextElapse|Result"
6
Managing Timers — Enable, Disable, Test
# Enable and start a timer
sudo systemctl enable --now nightly-backup.timer

# Disable a timer (stops it running at boot)
sudo systemctl disable nightly-backup.timer

# Stop a running timer temporarily
sudo systemctl stop nightly-backup.timer

# Manually trigger the service NOW (for testing)
sudo systemctl start nightly-backup.service

# Check timer status
systemctl status nightly-backup.timer

# Check service logs
journalctl -u nightly-backup.service
journalctl -u nightly-backup.service -n 50
journalctl -u nightly-backup.service --since today

# Check both timer and service logs together
journalctl -u nightly-backup.timer -u nightly-backup.service
💡 Test by running the service directly. Don't wait for the timer to fire to test your job — run sudo systemctl start yourjob.service immediately and check with journalctl -u yourjob.service. Fix issues before the timer ever fires.
7
User Timers — Without Root

Systemd timers can run as regular users — no root required:

# Create user timer directories
mkdir -p ~/.config/systemd/user/

# Create user service
cat > ~/.config/systemd/user/sync-notes.service << 'EOF'
[Unit]
Description=Sync notes to remote

[Service]
Type=oneshot
ExecStart=/usr/bin/rsync -av ~/notes/ user@remote:/backup/notes/
EOF

# Create user timer
cat > ~/.config/systemd/user/sync-notes.timer << 'EOF'
[Unit]
Description=Sync notes every 30 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true

[Install]
WantedBy=timers.target
EOF

# Enable and start (no sudo needed)
systemctl --user daemon-reload
systemctl --user enable --now sync-notes.timer

# Check status
systemctl --user list-timers
systemctl --user status sync-notes.timer
journalctl --user -u sync-notes.service

# Enable linger so user timers survive logout
loginctl enable-linger $USER
loginctl enable-linger is required if you want user timers to keep running after you log out. Without it, your user session ends on logout and all user timers stop.
8
Cron vs Systemd Timers — Migration Guide

Converting existing cron jobs to systemd timers:

# OLD cron job:
# 30 2 * * * root /usr/local/bin/nightly-backup.sh

# NEW — nightly-backup.service
cat > /etc/systemd/system/nightly-backup.service << 'EOF'
[Unit]
Description=Nightly Backup
After=network.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/nightly-backup.sh
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nightly-backup
EOF

# NEW — nightly-backup.timer
cat > /etc/systemd/system/nightly-backup.timer << 'EOF'
[Unit]
Description=Nightly Backup Timer
Requires=nightly-backup.service

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now nightly-backup.timer
FeatureCronSystemd Timer
Setup complexitySimple — one lineTwo unit files needed
LoggingEmail or redirect to filejournald — always logged
Missed job recoveryNo (use anacron)Yes — Persistent=true
DependenciesNoneFull systemd dependency ordering
Resource limitsNoneCPUQuota, MemoryLimit etc.
User timersPer-user crontab--user mode with linger
Random delayManual sleep in scriptRandomizedDelaySec=
View next runCalculate manuallysystemctl list-timers

Quick Reference

CommandWhat it does
systemctl list-timersList active timers with next/last run
systemctl list-timers --allList all timers including inactive
systemctl enable --now NAME.timerEnable and start a timer
systemctl disable NAME.timerDisable timer at boot
systemctl stop NAME.timerStop timer temporarily
systemctl start NAME.serviceManually trigger the job now
systemctl status NAME.timerTimer status and next run
journalctl -u NAME.serviceView job output logs
systemd-analyze calendar "expr"Verify calendar expression
systemctl --user list-timersUser timers
loginctl enable-linger $USERKeep user timers alive after logout

OnCalendar Quick Reference

ExpressionMeaning
dailyEvery day at midnight
weeklyEvery Monday at midnight
monthlyFirst of month at midnight
*-*-* 02:30:00Every day at 2:30 AM
Mon *-*-* 08:00:00Every Monday at 8 AM
Mon-Fri *-*-* 08:00:00Weekdays at 8 AM
*:0/15Every 15 minutes
*-01-01 00:00:00Every January 1st