systemd Timers

systemd timer units as the modern cron replacement. OnCalendar expressions, persistent scheduling, and resource limits.

Timer Basics

Timer unit file — the modern cron replacement
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer

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

[Install]
WantedBy=timers.target

Persistent=true means if the machine was off at 02:30, the timer fires on next boot. RandomizedDelaySec adds jitter to avoid thundering herd when multiple timers share a schedule.

Matching service unit — the timer triggers this
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
After=network-online.target

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

Timer and service share the same name prefix (backup.timer triggers backup.service). No [Install] section needed on the service — the timer handles activation.

OnCalendar Expressions

Calendar expressions — more readable than cron syntax
OnCalendar=daily                    # 00:00:00 every day
OnCalendar=weekly                   # Monday 00:00:00
OnCalendar=monthly                  # 1st of month 00:00:00
OnCalendar=*-*-* 06:00:00          # daily at 6 AM
OnCalendar=Mon *-*-* 08:00:00      # every Monday at 8 AM
OnCalendar=*-*-01 00:00:00         # first of every month
OnCalendar=*-*-* *:00/15:00        # every 15 minutes
OnCalendar=Mon..Fri *-*-* 18:00:00 # weekdays at 6 PM
Validate a calendar expression — before deploying
systemd-analyze calendar "Mon *-*-* 08:00:00"
  Original form: Mon *-*-* 08:00:00
Normalized form: Mon *-*-* 08:00:00
    Next elapse: Mon 2026-04-13 08:00:00 PDT
       (in UTC): Mon 2026-04-13 15:00:00 UTC
       From now: 2 days left
Monotonic timers — relative to boot or activation
[Timer]
OnBootSec=5min            # 5 minutes after boot
OnUnitActiveSec=1h        # 1 hour after the service last started
OnStartupSec=10min        # 10 minutes after systemd starts

Management

Enable, start, and check timer status
# Enable and start the timer
sudo systemctl enable --now backup.timer

# List all active timers — shows next/last trigger times
systemctl list-timers --all

# Check a specific timer
systemctl status backup.timer

# Check the associated service (last run output)
systemctl status backup.service
journalctl -u backup.service --since today
Manually trigger the service — test without waiting for the timer
sudo systemctl start backup.service
journalctl -u backup.service -f

User Timers

Per-user timer — no root needed
# Create timer in user directory
mkdir -p ~/.config/systemd/user/

# ~/.config/systemd/user/sync-notes.timer
# Same format, just under user directory

# Enable as user
systemctl --user enable --now sync-notes.timer
systemctl --user list-timers

User timers run under the user’s session. They require loginctl enable-linger <user> to run when the user is not logged in.

Transient Timers

One-shot scheduled task — no unit files needed
# Run a command 30 minutes from now
systemd-run --on-active=30m /usr/local/bin/cleanup.sh

# Run at a specific time
systemd-run --on-calendar="2026-04-11 14:00:00" /usr/local/bin/deploy.sh

# With a description
systemd-run --on-active=1h --unit=reminder --description="Meeting reminder" \
    notify-send "Team standup in 5 minutes"

Transient timers disappear after they fire. No cleanup needed.

Timer vs Cron Comparison

Feature comparison
Feature              systemd timer          cron
─────────────────────────────────────────────────────
Logging              journalctl             manual redirect
Dependencies         After=, Requires=      none
Missed runs          Persistent=true        anacron (separate)
Randomized delay     RandomizedDelaySec=    not built-in
Resource limits      CPUQuota=, MemoryMax=  not built-in
Per-user             systemctl --user       crontab -e
Calendar syntax      OnCalendar=            5-field cron
Portability          systemd only           all Unix

Practical Example

Complete backup timer with resource limits
# /etc/systemd/system/site-backup.timer
[Unit]
Description=Nightly site backup

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=600

[Install]
WantedBy=timers.target
# /etc/systemd/system/site-backup.service
[Unit]
Description=Nightly site backup
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/site-backup.sh
User=backup
Nice=10
IOSchedulingClass=idle
CPUQuota=50%
MemoryMax=512M
StandardOutput=journal
StandardError=journal

Nice=10 and IOSchedulingClass=idle ensure the backup does not starve interactive workloads. CPUQuota and MemoryMax are hard limits.

See Also

  • Cron — traditional cron scheduling

  • systemd — service management fundamentals