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.
systemctlsystemctl list-timerssystemd-analyze calendarsystemd-runOnCalendarPersistent
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):
| Setting | Meaning |
|---|---|
OnCalendar=*-*-* 02:15:00 | Every day at 02:15 |
OnCalendar=Mon..Fri 09:00 | Weekdays at 09:00 |
OnCalendar=hourly / daily / weekly | Shortcut names |
OnBootSec=5min | Five minutes after boot |
OnUnitActiveSec=1h | One hour after the service last ran |
Persistent=true | Catch 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.
- 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-timersincludessystemd-tmpfiles-clean.timeramong others.Hint
systemctl list-timers --allalso shows inactive ones.Solution
systemctl list-timers --no-pager - Practice
Test an expression before using it
Check, with
systemd-analyze calendar, when each of these next fires:daily,Mon..Fri 09:00and*:0/15. State in words what the third one means.Check your result
Each expression prints a
Normalized formandNext elapse.*:0/15is 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' - Practice
A daily backup timer
Create
backup.servicethat runs/usr/local/bin/backup.shonce (create the script and make it log withlogger), andbackup.timerthat 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.timershowsbackup.timerwith a NEXT time of 02:15.systemctl cat backup.timershowsPersistent=true.Hint
The service needs
Type=oneshot. The timer needsOnCalendar=--* 02:15:00andPersistent=true, and an[Install]section withWantedBy=timers.target. Runsystemctl daemon-reloadafter 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 - 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.serviceincludes abackup ranmessage fromloggeror 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 - Challenge
A throwaway delayed job
Without writing any unit files, run
touch /tmp/ran-by-timer30 seconds from now using a transient timer. Show the transient timer, wait, and prove the file exists. Then explain how this differs fromat.Check your result
systemctl list-timersshows arun-*.timerbefore it fires andls /tmp/ran-by-timersucceeds 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