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 OnCalendar=.

Monotonic

Triggers relative to events. Uses OnBootSec=, OnUnitActiveSec=, etc.

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

/etc/systemd/system/mytask.timer
[Unit]
Description=Run my task daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
/etc/systemd/system/mytask.service
[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/
~/.config/systemd/user/backup.timer
[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

minutely

Every minute (::00)

hourly

Every hour (*:00:00)

daily

Every day at midnight (00:00:00)

weekly

Every Monday at midnight

monthly

First of month at midnight

yearly / annually

January 1st at midnight

quarterly

First of quarter at midnight

semiannually

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

Option Description

OnActiveSec=

Relative to timer activation

OnBootSec=

Relative to machine boot

OnStartupSec=

Relative to systemd start (user: login)

OnUnitActiveSec=

Relative to when unit was last activated

OnUnitInactiveSec=

Relative to when unit became inactive

Example: Watchdog Timer

[Unit]
Description=Run health check every 5 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
AccuracySec=1s

[Install]
WantedBy=timers.target

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

Wake from Suspend

[Timer]
OnCalendar=*-*-* 04:00:00
# Wake system from suspend/hibernate
WakeSystem=true

Specifying Service Unit

[Timer]
OnCalendar=hourly
# By default, mytask.timer runs mytask.service
# Override with Unit=
Unit=different-task.service

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

Check Status

# Timer status
systemctl status mytask.timer

# Service status
systemctl status mytask.service

# Show timer properties
systemctl show mytask.timer

# Check if enabled
systemctl is-enabled mytask.timer

Manually Trigger

# Run the service now (bypasses timer)
systemctl start mytask.service

# Reset timer (restart countdown)
systemctl restart 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

/etc/systemd/system/backup.timer
[Unit]
Description=Daily backup at 2am

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

[Install]
WantedBy=timers.target
/etc/systemd/system/backup.service
[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

/etc/systemd/system/logrotate.timer
[Unit]
Description=Daily log rotation

[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true

[Install]
WantedBy=timers.target

Certificate Renewal

/etc/systemd/system/certbot-renew.timer
[Unit]
Description=Certbot renewal timer

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
/etc/systemd/system/certbot-renew.service
[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

/etc/systemd/system/disk-monitor.timer
[Unit]
Description=Check disk space every 15 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
AccuracySec=1min

[Install]
WantedBy=timers.target
/etc/systemd/system/disk-monitor.service
[Unit]
Description=Disk space monitor

[Service]
Type=oneshot
ExecStart=/usr/local/bin/check-disk-space.sh

Working Hours Only

/etc/systemd/system/work-task.timer
[Unit]
Description=Task runs only during work hours

[Timer]
# Every 30 minutes, Mon-Fri, 9am-5pm
OnCalendar=Mon..Fri *-*-* 09..17:0/30:00
Persistent=true

[Install]
WantedBy=timers.target

Migration from Cron

Cron to Timer Conversion

Cron Expression OnCalendar Equivalent

0 * * * *

*:00:00 or hourly

*/15 * * * *

*:0/15:00

0 0 * * *

--* 00:00:00 or daily

0 0 * * 0

Sun --* 00:00:00 or weekly

0 0 1 * *

--01 00:00:00 or monthly

0 0 1 1 *

*-01-01 00:00:00 or yearly

0 9 * * 1-5

Mon..Fri --* 09:00:00

*/5 9-17 * * *

--* 09..17:0/5:00

@reboot

OnBootSec=0

Example Migration

Cron entry:

30 2 * * * /usr/local/bin/nightly-job.sh

Equivalent timer:

/etc/systemd/system/nightly-job.timer
[Unit]
Description=Nightly job at 2:30am

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

[Install]
WantedBy=timers.target
/etc/systemd/system/nightly-job.service
[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

Missed Timers

# Check if Persistent is set
systemctl show mytask.timer | grep Persistent

# Check last trigger time
systemctl show mytask.timer -p LastTriggerUSec

# Check for system suspend/hibernate
journalctl -b | grep -i "suspend\|hibernate"

Debug Mode

# Increase logging
systemctl edit mytask.service
# Add:
# [Service]
# Environment=DEBUG=1

# Watch timer in real-time
watch systemctl list-timers

# Journal follow
journalctl -f -u mytask.timer -u 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

See Also