systemd Timers
Quick Reference
# List all timers
systemctl list-timers --all
# Start/stop timer
systemctl start mytimer.timer
systemctl stop mytimer.timer
# Enable at boot
systemctl enable mytimer.timer
# View timer status
systemctl status mytimer.timer
# Test calendar expression
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
# Run associated service now
systemctl start mytimer.service
Understanding Timers
Timers vs Cron
| Feature | cron | systemd Timers |
|---|---|---|
Syntax |
5-field cron expression |
OnCalendar or OnBootSec/etc |
Dependencies |
None |
Full systemd dependency management |
Logging |
Separate (syslog/mail) |
Integrated with journald |
Missed runs |
Lost unless using anacron |
Persistent option catches up |
Resource control |
None |
Full cgroup integration |
Boot-time scheduling |
@reboot only |
OnBootSec, OnStartupSec, etc |
Security |
Limited |
Full sandboxing options |
Timer Types
| Type | Description |
|---|---|
Realtime (wallclock) |
Triggers at specific times (like cron). Uses |
Monotonic |
Triggers relative to events. Uses |
Timer Architecture
┌──────────────────────────┐
│ mytask.timer │ <-- Timer unit (when to run)
│ OnCalendar=daily │
│ Unit=mytask.service │ <-- Points to service
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ mytask.service │ <-- Service unit (what to run)
│ ExecStart=/usr/bin/task │
└──────────────────────────┘
Creating Timers
Basic Timer Unit
[Unit]
Description=Run my task daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
[Unit]
Description=My scheduled task
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mytask.sh
# Enable and start
systemctl daemon-reload
systemctl enable --now mytask.timer
Timer with User Context
For user timers, place in ~/.config/systemd/user/:
mkdir -p ~/.config/systemd/user/
[Unit]
Description=Daily backup timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now backup.timer
OnCalendar Syntax
Format
OnCalendar=DayOfWeek Year-Month-Day Hour:Minute:Second
Examples:
*-*-* *:*:00 # Every minute
*-*-* *:00:00 # Every hour
*-*-* 00:00:00 # Daily at midnight
Mon *-*-* 00:00:00 # Every Monday at midnight
Mon..Fri *-*-* 09:00 # Weekdays at 9am
*-*-01 00:00:00 # First of every month
*-01-01 00:00:00 # January 1st yearly
Common Expressions
| Expression | Description |
|---|---|
|
Every minute ( |
|
Every hour ( |
|
Every day at midnight ( |
|
Every Monday at midnight |
|
First of month at midnight |
|
January 1st at midnight |
|
First of quarter at midnight |
|
January 1st and July 1st |
Advanced Expressions
# Every 15 minutes
OnCalendar=*:0/15
# Every 2 hours
OnCalendar=0/2:00:00
# Weekdays at 9am and 5pm
OnCalendar=Mon..Fri *-*-* 09,17:00:00
# Last day of month (using OnCalendar isn't ideal, use service logic)
# Alternative: 28..31 of each month
OnCalendar=*-*-28..31 00:00:00
# Specific dates
OnCalendar=2024-12-25 00:00:00
# Multiple times
OnCalendar=Mon *-*-* 10:00:00
OnCalendar=Thu *-*-* 14:00:00
# Every 5 minutes during work hours
OnCalendar=Mon..Fri *-*-* 09..17:0/5:00
Test Calendar Expressions
# Parse and show next trigger times
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
# Output:
# Original form: Mon..Fri *-*-* 09:00:00
# Normalized form: Mon..Fri *-*-* 09:00:00
# Next elapse: Mon 2024-01-15 09:00:00 UTC
# From now: 2 days left
# Show multiple future times
systemd-analyze calendar --iterations=5 "daily"
# Verify timer
systemd-analyze verify /etc/systemd/system/mytask.timer
Monotonic Timers
Boot-Relative Timers
[Timer]
# Run 5 minutes after boot
OnBootSec=5min
# Run 1 hour after boot
OnBootSec=1h
# Run 30 seconds after boot
OnBootSec=30
Activation-Relative Timers
[Timer]
# Run every 15 minutes after last service completion
OnUnitActiveSec=15min
# Run every hour after last service activation
OnUnitActiveSec=1h
# Combination: first run 5min after boot, then every hour
OnBootSec=5min
OnUnitActiveSec=1h
Timer Options
Accuracy
[Timer]
# Default is 1 minute - systemd batches timers for efficiency
AccuracySec=1min
# For precise timing (uses more resources)
AccuracySec=1s
# For approximate timing (saves power)
AccuracySec=1h
Persistent Timers
[Timer]
OnCalendar=daily
# Catch up if timer was missed (e.g., system was off)
Persistent=true
Randomized Delay
[Timer]
OnCalendar=daily
# Add random delay up to 1 hour (prevents thundering herd)
RandomizedDelaySec=1h
Managing Timers
List Timers
# List active timers
systemctl list-timers
# List all timers (including inactive)
systemctl list-timers --all
# User timers
systemctl --user list-timers
# Output columns:
# NEXT - Next time timer will fire
# LEFT - Time until next trigger
# LAST - Last time timer fired
# PASSED - Time since last trigger
# UNIT - Timer unit name
# ACTIVATES - Service unit it triggers
Enable/Disable Timers
# Enable and start
systemctl enable --now mytask.timer
# Enable only (starts on next boot)
systemctl enable mytask.timer
# Disable
systemctl disable mytask.timer
# Stop running timer
systemctl stop mytask.timer
Transient Timers
Run one-time or temporary scheduled tasks:
# Run command in 5 minutes
systemd-run --on-active=5m /usr/local/bin/task.sh
# Run at specific time
systemd-run --on-calendar="2024-01-15 10:00:00" /usr/local/bin/task.sh
# Run after boot
systemd-run --on-boot=10m /usr/local/bin/startup-task.sh
# With description
systemd-run --on-active=1h --description="Cleanup task" /usr/local/bin/cleanup.sh
# User context
systemd-run --user --on-active=5m /home/user/script.sh
# With service options
systemd-run --on-active=5m \
--property=Type=oneshot \
--property=User=nobody \
/usr/local/bin/task.sh
# List transient timers
systemctl list-timers --all | grep "run-"
Examples
Daily Backup
[Unit]
Description=Daily backup at 2am
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=30min
[Install]
WantedBy=timers.target
[Unit]
Description=Backup service
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Group=backup
Nice=19
IOSchedulingClass=idle
Log Rotation
[Unit]
Description=Daily log rotation
[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true
[Install]
WantedBy=timers.target
Certificate Renewal
[Unit]
Description=Certbot renewal timer
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
[Unit]
Description=Certbot renewal
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
ExecStartPost=/bin/systemctl reload nginx
Disk Space Monitor
[Unit]
Description=Check disk space every 15 minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
AccuracySec=1min
[Install]
WantedBy=timers.target
[Unit]
Description=Disk space monitor
[Service]
Type=oneshot
ExecStart=/usr/local/bin/check-disk-space.sh
Migration from Cron
Cron to Timer Conversion
| Cron Expression | OnCalendar Equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Example Migration
Cron entry:
30 2 * * * /usr/local/bin/nightly-job.sh
Equivalent timer:
[Unit]
Description=Nightly job at 2:30am
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
[Unit]
Description=Nightly job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/nightly-job.sh
Troubleshooting
Timer Not Running
# Check timer status
systemctl status mytask.timer
# Check if enabled
systemctl is-enabled mytask.timer
# Check next run time
systemctl list-timers mytask.timer
# Verify calendar expression
systemd-analyze calendar "your-expression"
# Check journal for errors
journalctl -u mytask.timer -u mytask.service
Service Fails
# Check service status
systemctl status mytask.service
# View logs
journalctl -u mytask.service -e
# Run manually to test
systemctl start mytask.service
# Check exit code
systemctl show -p ExecMainStatus mytask.service
Quick Command Reference
# List timers
systemctl list-timers # Active timers
systemctl list-timers --all # All timers
systemctl --user list-timers # User timers
# Manage timers
systemctl enable --now mytask.timer # Enable and start
systemctl disable mytask.timer # Disable
systemctl start mytask.service # Run now
# Status
systemctl status mytask.timer
systemctl status mytask.service
# Test calendar
systemd-analyze calendar "daily"
systemd-analyze calendar --iterations=10 "*:0/15"
# Transient timers
systemd-run --on-active=5m /path/to/script
systemd-run --on-calendar="Mon *-*-* 09:00" /path/to/script
# Logs
journalctl -u mytask.timer
journalctl -u mytask.service