How To

Configure Systemd Timers on Linux (Practical Examples)

Systemd timers do everything cron does, plus some things cron cannot: they log to the journal, they can catch up on missed runs after a reboot, they support randomized delays to avoid thundering herds, and you can test them with systemctl start without waiting for the schedule to trigger. If your server runs systemd (which is every major distro since 2015), you already have timers available.

Original content from computingforgeeks.com - post 72493

This guide covers creating systemd timers from scratch on Ubuntu 24.04, with practical examples you can adapt: disk monitoring, automated backups, log cleanup, user-level timers, and transient one-off timers. Every command and output below was tested on a real VM. For background on managing systemd services with systemctl, see our reference guide.

Tested March 2026 on Ubuntu 24.04 LTS with systemd 255

How Systemd Timers Work

A systemd timer consists of two unit files that work together:

  • A .timer unit that defines the schedule (when to run)
  • A .service unit that defines the task (what to run)

The timer activates the service when the schedule triggers. By default, a timer named backup.timer activates backup.service (matching name, different suffix). Both files go in /etc/systemd/system/ for system-wide timers.

There are two types of timers:

TypeTriggerExample
MonotonicRelative to an event (boot, last run)OnBootSec=5min, OnUnitActiveSec=1h
CalendarAt specific dates/timesOnCalendar=*-*-* 02:00:00 (daily at 2am)

Monotonic timers run relative to when the system booted or when the service last ran. Calendar timers run at specific wall-clock times, like cron.

Example 1: Disk Usage Monitor (Every 5 Minutes)

This timer checks root partition usage every 5 minutes and logs a warning when it exceeds 80%. It demonstrates a monotonic timer with OnUnitActiveSec.

Create the script that does the actual work:

sudo tee /usr/local/bin/disk-monitor.sh > /dev/null << 'EOF'
#!/bin/bash
USAGE=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "$TIMESTAMP - Root partition usage: ${USAGE}%" >> /var/log/disk-monitor.log
if [ "$USAGE" -gt 80 ]; then
    echo "$TIMESTAMP - WARNING: Disk usage above 80%!" >> /var/log/disk-monitor.log
fi
EOF
sudo chmod +x /usr/local/bin/disk-monitor.sh

Create the service unit that runs the script:

sudo tee /etc/systemd/system/disk-monitor.service > /dev/null << 'EOF'
[Unit]
Description=Monitor disk usage and log alerts

[Service]
Type=oneshot
ExecStart=/usr/local/bin/disk-monitor.sh
EOF

The Type=oneshot tells systemd the service runs once and exits (not a long-running daemon). This is the correct type for timer-triggered tasks.

Create the timer unit:

sudo tee /etc/systemd/system/disk-monitor.timer > /dev/null << 'EOF'
[Unit]
Description=Run disk monitor every 5 minutes

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

[Install]
WantedBy=timers.target
EOF

OnBootSec=60 runs the first check 60 seconds after boot. OnUnitActiveSec=5min repeats every 5 minutes after each run. AccuracySec=1s ensures the timer fires within 1 second of the scheduled time (the default is 1 minute, which batches timers for power efficiency).

Reload systemd, enable, and start the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now disk-monitor.timer

Verify the timer is active and scheduled:

systemctl status disk-monitor.timer

The output shows when the next trigger will fire:

● disk-monitor.timer - Run disk monitor every 5 minutes
     Loaded: loaded (/etc/systemd/system/disk-monitor.timer; enabled; preset: enabled)
     Active: active (waiting) since Wed 2026-03-25 11:02:37 UTC; 9ms ago
    Trigger: Wed 2026-03-25 11:07:45 UTC; 4min left
   Triggers: ● disk-monitor.service

Test the service manually without waiting for the timer:

sudo systemctl start disk-monitor.service
cat /var/log/disk-monitor.log

The log confirms it ran:

2026-03-25 11:02:37 - Root partition usage: 11%

Example 2: Daily Backup at 2 AM (Calendar Timer)

This timer runs a backup script every day at 2:00 AM. It uses OnCalendar for wall-clock scheduling, Persistent=true to catch up if the server was off at 2 AM, and RandomizedDelaySec to avoid all servers hitting the backup target at exactly the same second.

Create the backup script:

sudo tee /usr/local/bin/daily-backup.sh > /dev/null << 'EOF'
#!/bin/bash
BACKUP_DIR="/var/backups/app"
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
mkdir -p "$BACKUP_DIR"
tar czf "$BACKUP_DIR/etc-backup-${TIMESTAMP}.tar.gz" /etc/hostname /etc/hosts 2>/dev/null
echo "$(date '+%Y-%m-%d %H:%M:%S') - Backup completed: etc-backup-${TIMESTAMP}.tar.gz" >> /var/log/backup.log
# Keep only last 7 backups
ls -t "$BACKUP_DIR"/etc-backup-*.tar.gz 2>/dev/null | tail -n +8 | xargs rm -f 2>/dev/null
EOF
sudo chmod +x /usr/local/bin/daily-backup.sh

Create the service unit:

sudo tee /etc/systemd/system/daily-backup.service > /dev/null << 'EOF'
[Unit]
Description=Daily backup of critical configs

[Service]
Type=oneshot
ExecStart=/usr/local/bin/daily-backup.sh
EOF

Create the timer with calendar scheduling:

sudo tee /etc/systemd/system/daily-backup.timer > /dev/null << 'EOF'
[Unit]
Description=Run daily backup at 2:00 AM

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

[Install]
WantedBy=timers.target
EOF

Persistent=true is the key difference from cron. If the server was powered off at 2 AM, systemd will run the backup immediately after the next boot. Cron would just skip it. RandomizedDelaySec=300 adds a random delay of up to 5 minutes, which prevents all servers in a fleet from starting their backups at the exact same second.

Enable and verify:

sudo systemctl daemon-reload
sudo systemctl enable --now daily-backup.timer
systemctl status daily-backup.timer

The timer shows the next trigger time with the randomized delay applied:

● daily-backup.timer - Run daily backup at 2:00 AM
     Loaded: loaded (/etc/systemd/system/daily-backup.timer; enabled; preset: enabled)
     Active: active (waiting) since Wed 2026-03-25 11:02:53 UTC
    Trigger: Thu 2026-03-26 02:02:27 UTC; 14h left
   Triggers: ● daily-backup.service

Test it manually:

sudo systemctl start daily-backup.service
cat /var/log/backup.log
2026-03-25 11:02:53 - Backup completed: etc-backup-20260325_110253.tar.gz

Example 3: Weekly Log Cleanup (Low Priority)

Maintenance tasks like log cleanup should run at low priority so they do not compete with production workloads. This timer demonstrates Nice=19 and IOSchedulingClass=idle to minimize system impact.

Create the cleanup script:

sudo tee /usr/local/bin/log-cleanup.sh > /dev/null << 'EOF'
#!/bin/bash
# Remove compressed logs older than 30 days
find /var/log -name "*.log.gz" -mtime +30 -delete 2>/dev/null
find /var/log -name "*.log.[0-9]" -mtime +30 -delete 2>/dev/null

# Truncate logs larger than 100MB
find /var/log -name "*.log" -size +100M -exec truncate -s 0 {} \; 2>/dev/null

# Clean systemd journal older than 7 days
journalctl --vacuum-time=7d --quiet 2>/dev/null

echo "$(date '+%Y-%m-%d %H:%M:%S') - Log cleanup completed" >> /var/log/cleanup.log
EOF
sudo chmod +x /usr/local/bin/log-cleanup.sh

Create the service with low CPU and I/O priority:

sudo tee /etc/systemd/system/log-cleanup.service > /dev/null << 'EOF'
[Unit]
Description=Clean old log files

[Service]
Type=oneshot
ExecStart=/usr/local/bin/log-cleanup.sh
Nice=19
IOSchedulingClass=idle
EOF

Nice=19 gives the process the lowest CPU priority. IOSchedulingClass=idle means it only does disk I/O when nothing else needs the disk. These settings are not available with cron.

Create the timer to run every Sunday at 3 AM:

sudo tee /etc/systemd/system/log-cleanup.timer > /dev/null << 'EOF'
[Unit]
Description=Weekly log cleanup

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
RandomizedDelaySec=1h

[Install]
WantedBy=timers.target
EOF

Enable and test:

sudo systemctl daemon-reload
sudo systemctl enable --now log-cleanup.timer
sudo systemctl start log-cleanup.service
systemctl status log-cleanup.service

The service exits cleanly with status=0/SUCCESS:

○ log-cleanup.service - Clean old log files
     Loaded: loaded (/etc/systemd/system/log-cleanup.service; static)
     Active: inactive (dead) since Wed 2026-03-25 11:03:57 UTC
TriggeredBy: ● log-cleanup.timer
    Process: 1934 ExecStart=/usr/local/bin/log-cleanup.sh (code=exited, status=0/SUCCESS)
   Main PID: 1934 (code=exited, status=0/SUCCESS)
        CPU: 12ms

Example 4: Transient Timer (No Files Needed)

Sometimes you need a quick one-off scheduled task without creating permanent unit files. systemd-run creates transient timers on the fly:

sudo systemd-run --on-active=30s --unit=quick-task /usr/local/bin/disk-monitor.sh

This runs disk-monitor.sh once, 30 seconds from now. The timer and service are created automatically and cleaned up after execution. You can verify it was scheduled:

systemctl list-timers quick-task*

Other useful systemd-run patterns:

# Run a command at a specific time today
sudo systemd-run --on-calendar="2026-03-25 15:00:00" /usr/local/bin/daily-backup.sh

# Run a command every 10 minutes (persists until reboot)
sudo systemd-run --on-active=10min --on-unit-active=10min /usr/local/bin/disk-monitor.sh

# Run with a description for easier identification
sudo systemd-run --on-active=5min --description="One-off DB export" pg_dump mydb > /tmp/export.sql

Example 5: User Timer (No Root Required)

User timers run under a regular user account without sudo. The unit files go in ~/.config/systemd/user/ instead of /etc/systemd/system/. This is useful for personal automation like syncing files or checking for updates.

Create the user service directory and files:

mkdir -p ~/.config/systemd/user/

tee ~/.config/systemd/user/user-task.service > /dev/null << 'EOF'
[Unit]
Description=User-level task example

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo "User timer ran at $(date)" >> %h/user-timer.log'
EOF

tee ~/.config/systemd/user/user-task.timer > /dev/null << 'EOF'
[Unit]
Description=Run user task every 2 minutes

[Timer]
OnBootSec=30
OnUnitActiveSec=2min

[Install]
WantedBy=timers.target
EOF

The %h specifier expands to the user's home directory. Enable and start with the --user flag:

systemctl --user daemon-reload
systemctl --user enable --now user-task.timer
systemctl --user status user-task.timer

The timer runs under your user account:

● user-task.timer - Run user task every 2 minutes
     Loaded: loaded (/home/ubuntu/.config/systemd/user/user-task.timer; enabled; preset: enabled)
     Active: active (running) since Wed 2026-03-25 11:03:10 UTC
    Trigger: n/a
   Triggers: ● user-task.service

For user timers to run when you are not logged in, enable lingering for your account:

sudo loginctl enable-linger $USER

OnCalendar Syntax Reference

The OnCalendar directive uses the format DayOfWeek Year-Month-Day Hour:Minute:Second. All fields are optional except at least one time component. The systemd.time documentation has the full specification.

ExpressionWhen It Runs
*-*-* *:00/15:00Every 15 minutes
hourlyEvery hour at :00
dailyEvery day at midnight
*-*-* 02:00:00Every day at 2:00 AM
weeklyEvery Monday at midnight
Mon *-*-* 09:00:00Every Monday at 9:00 AM
Mon..Fri *-*-* 18:00:00Weekdays at 6:00 PM
*-*-01 03:00:00First of every month at 3:00 AM
*-01,04,07,10-01 00:00:00Quarterly (Jan, Apr, Jul, Oct 1st)
Sun *-*-* 03:00:00Every Sunday at 3:00 AM
monthlyFirst of every month at midnight
yearlyJanuary 1st at midnight

Validate any expression before deploying with systemd-analyze calendar:

systemd-analyze calendar "Mon..Fri *-*-* 18:00:00"

The output shows the normalized form and the next trigger time:

Normalized form: Mon..Fri *-*-* 18:00:00
    Next elapse: Wed 2026-03-25 18:00:00 UTC
       From now: 6h left

This catches syntax errors before they cause a timer to never fire. Always test expressions with systemd-analyze calendar first.

Timer Configuration Reference

Key directives for the [Timer] section:

DirectivePurposeExample
OnBootSecRun N seconds/minutes after bootOnBootSec=5min
OnUnitActiveSecRepeat N after each runOnUnitActiveSec=1h
OnCalendarWall-clock scheduleOnCalendar=daily
PersistentCatch up missed runs after downtimePersistent=true
RandomizedDelaySecAdd random delay to avoid thundering herdRandomizedDelaySec=5min
AccuracySecTimer precision (default 1min)AccuracySec=1s
OnStartupSecRun N after systemd starts (user timers)OnStartupSec=30
OnUnitInactiveSecRun N after service becomes inactiveOnUnitInactiveSec=30min
UnitOverride which service to triggerUnit=other.service

List and Monitor Timers

View all active timers on the system:

systemctl list-timers

The output shows the next trigger time, time until trigger, last run, and which service each timer activates:

NEXT                            LEFT    LAST                         PASSED  UNIT               ACTIVATES
Wed 2026-03-25 11:07:45 UTC 3min 48s    Wed 2026-03-25 11:02:45 UTC 1min ago disk-monitor.timer disk-monitor.service
Thu 2026-03-26 02:02:01 UTC      14h    -                                 -  daily-backup.timer daily-backup.service
Sun 2026-03-29 03:08:26 UTC   3 days    -                                 -  log-cleanup.timer  log-cleanup.service

Include inactive timers with --all:

systemctl list-timers --all

Check the journal for timer-triggered service runs:

journalctl -u disk-monitor.service --no-pager -n 10

The journal shows exact start/finish times and exit status for every run:

Mar 25 11:02:45 ubuntu systemd[1]: Starting disk-monitor.service - Monitor disk usage and log alerts...
Mar 25 11:02:45 ubuntu systemd[1]: disk-monitor.service: Deactivated successfully.
Mar 25 11:02:45 ubuntu systemd[1]: Finished disk-monitor.service - Monitor disk usage and log alerts.

Stop and Disable Timers

Stop a running timer (it will not fire again until manually started or re-enabled after reboot):

sudo systemctl stop disk-monitor.timer

Disable it so it does not start on boot:

sudo systemctl disable disk-monitor.timer

Remove the timer and service files entirely:

sudo systemctl stop disk-monitor.timer
sudo systemctl disable disk-monitor.timer
sudo rm /etc/systemd/system/disk-monitor.timer /etc/systemd/system/disk-monitor.service
sudo systemctl daemon-reload

Systemd Timers vs Cron

FeatureSystemd TimersCron
Catch up missed runsYes (Persistent=true)No (missed = skipped)
LoggingJournald (structured, queryable)Email or syslog
Test without waitingsystemctl start serviceNot possible
Random delayRandomizedDelaySecManual sleep $RANDOM
CPU/IO priorityNice=, IOSchedulingClass=Must set in script
Resource limitscgroups (MemoryMax, CPUQuota)None built-in
DependenciesAfter=, Requires=None
ConfigurationTwo files (.timer + .service)One line in crontab
Second precisionYesMinute only
User-level taskssystemctl --usercrontab -e

Cron is still the faster option for simple, single-line scheduled commands. When you need logging, missed-run recovery, resource controls, or dependency ordering, systemd timers with persistent journal storage are the better choice.

Related Articles

How To Install NixOS on Proxmox from ISO (with GPT Partitioning) Security 5 Security Tips for Linux Users AlmaLinux Setup Private Docker Registry on Rocky Linux 10 / AlmaLinux 10 Monitoring Install and Configure Nagios 4.x on Oracle Linux 9

Leave a Comment

Press ESC to close