systemd Timers
systemd timer units as the modern cron replacement. OnCalendar expressions, persistent scheduling, and resource limits.
Timer Basics
# /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.
# /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
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
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
[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 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
sudo systemctl start backup.service
journalctl -u backup.service -f
User Timers
# 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
# 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 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
# /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.