2 of 3

Scheduling and TimeLesson 2 of 3

Scheduling with systemd timers

Pair a service with a timer, write a calendar expression, and let systemd catch up on missed runs.

  • systemctl
  • systemctl list-timers
  • systemd-analyze calendar
  • systemd-run
  • OnCalendar
  • Persistent

40 min5 exercises

After this lesson you can

  • Write a .service and .timer pair, enable the timer, and list its next run.
  • Test calendar expressions before using them.
  • Run a quick one-off or delayed command with systemd-run, and read a timer's logs.

The idea

A timer unit (name.timer) starts a service unit of the same name (name.service) on a schedule. Compared with cron you get: logging in the journal per job, dependencies and resource limits from the service, and Persistent=true, which runs a missed job after a reboot or downtime.

A timer can be calendar-based (OnCalendar=, like cron) or monotonic (relative to an event):

SettingMeaning
OnCalendar=*-*-* 02:15:00Every day at 02:15
OnCalendar=Mon..Fri 09:00Weekdays at 09:00
OnCalendar=hourly / daily / weeklyShortcut names
OnBootSec=5minFive minutes after boot
OnUnitActiveSec=1hOne hour after the service last ran
Persistent=trueCatch up on a missed calendar run

Your own units belong in /etc/systemd/system/. After you create or edit a unit file run systemctl daemon-reload so systemd re-reads it. Enable the timer, not the service.

Try it

systemctl list-timers --no-pager
NEXT                        LEFT LAST                        PASSED UNIT                         ACTIVATES
Tue 2026-11-03 12:00:00 +03 18min Tue 2026-11-03 11:00:12 +03 41min sysstat-collect.timer        sysstat-collect.service
Wed 2026-11-04 00:00:00 +03 12h   Tue 2026-11-03 00:00:01 +03 11h   logrotate.timer              logrotate.service
Wed 2026-11-04 10:12:55 +03 22h   Tue 2026-11-03 10:12:55 +03 1h    systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service

Test an expression first

systemd-analyze calendar 'Mon..Fri 09:00'
  Original form: Mon..Fri 09:00
Normalized form: Mon..Fri *-*-* 09:00:00
    Next elapse: Wed 2026-11-04 09:00:00 +03
       (in UTC): Wed 2026-11-04 06:00:00 UTC
       From now: 21h left

The two files

[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
[Unit]
Description=Run the backup every night

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

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer --no-pager
Created symlink '/etc/systemd/system/timers.target.wants/backup.timer' → '/etc/systemd/system/backup.timer'.
NEXT                        LEFT LAST PASSED UNIT         ACTIVATES
Wed 2026-11-04 02:15:00 +03 14h  -    -      backup.timer backup.service

Run the service by hand to test it without waiting for the schedule:

sudo systemctl start backup.service
sudo journalctl -u backup.service --no-pager | tail -2
Nov 03 11:55:31 rhel10 systemd[1]: Starting backup.service - Nightly backup...
Nov 03 11:55:31 rhel10 systemd[1]: Finished backup.service - Nightly backup.

One-off jobs with systemd-run

systemd-run creates a transient service, and with --on-active or --on-calendar a transient timer, with no files to clean up:

sudo systemd-run --on-active=30s touch /tmp/ran-by-timer
Running timer as unit: run-r1b2c3.timer
Will run service as unit: run-r1b2c3.service

Going further

Cron needs one line; a timer needs two files. Choose timers when you want logging, Persistent=, randomised delay (RandomizedDelaySec=) or dependencies on other units, and cron or at for quick personal jobs. systemctl edit --full backup.timer edits a unit and reloads for you, and systemctl status backup.timer shows the last trigger. To run user-level timers without root, put units in ~/.config/systemd/user/ and use systemctl --user.

Practice

0 of 5 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    See what is already scheduled

    List all active timers on the system and identify one that cleans temporary files.

    Check your result

    systemctl list-timers includes systemd-tmpfiles-clean.timer among others.

    Hint

    systemctl list-timers --all also shows inactive ones.

    Solution
    systemctl list-timers --no-pager
  2. Practice

    Test an expression before using it

    Check, with systemd-analyze calendar, when each of these next fires: daily, Mon..Fri 09:00 and *:0/15. State in words what the third one means.

    Check your result

    Each expression prints a Normalized form and Next elapse. *:0/15 is every 15 minutes, on the hour and at quarter past, half past and quarter to.

    Hint

    systemd-analyze calendar 'Mon..Fri 09:00'

    Solution
    systemd-analyze calendar daily
    systemd-analyze calendar 'Mon..Fri 09:00'
    systemd-analyze calendar '*:0/15'
  3. Practice

    A daily backup timer

    Create backup.service that runs /usr/local/bin/backup.sh once (create the script and make it log with logger), and backup.timer that fires it every day at 02:15 and, if the machine was off, runs it at the next boot. Enable the timer and show its next run.

    Check your result

    systemctl list-timers backup.timer shows backup.timer with a NEXT time of 02:15. systemctl cat backup.timer shows Persistent=true.

    Hint

    The service needs Type=oneshot. The timer needs OnCalendar=--* 02:15:00 and Persistent=true, and an [Install] section with WantedBy=timers.target. Run systemctl daemon-reload after writing units.

    Solution
    printf '#!/bin/bash\nlogger "backup ran"\n' | sudo tee /usr/local/bin/backup.sh
    sudo chmod 755 /usr/local/bin/backup.sh
    sudo tee /etc/systemd/system/backup.service <<'EOT'
    [Unit]
    Description=Nightly backup
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/backup.sh
    EOT
    sudo tee /etc/systemd/system/backup.timer <<'EOT'
    [Unit]
    Description=Run the backup every night
    
    [Timer]
    OnCalendar=*-*-* 02:15:00
    Persistent=true
    
    [Install]
    WantedBy=timers.target
    EOT
    sudo systemctl daemon-reload
    sudo systemctl enable --now backup.timer
    systemctl list-timers backup.timer --no-pager
  4. Practice

    Test the service without waiting

    Run the backup service once, right now, without touching the timer, and read its log lines from the journal.

    Check your result

    journalctl -u backup.service includes a backup ran message from logger or the service's start and finish lines.

    Hint

    Start the service unit, not the timer: systemctl start backup.service.

    Solution
    sudo systemctl start backup.service
    sudo journalctl -u backup.service --no-pager | tail -3
  5. Challenge

    A throwaway delayed job

    Without writing any unit files, run touch /tmp/ran-by-timer 30 seconds from now using a transient timer. Show the transient timer, wait, and prove the file exists. Then explain how this differs from at.

    Check your result

    systemctl list-timers shows a run-*.timer before it fires and ls /tmp/ran-by-timer succeeds afterwards.

    Hint

    systemd-run --on-active=30s command. Transient units disappear after they run.

    Solution
    sudo systemd-run --on-active=30s touch /tmp/ran-by-timer
    systemctl list-timers --no-pager | grep run-
    sleep 35
    ls /tmp/ran-by-timer