systemd timers are the modern alternative to cron. They schedule the run of systemd services on a calendar (OnCalendar) or after a delay (OnBootSec, OnUnitActiveSec). Their main advantage: logging through journalctl, dependency management and the ability to catch up with missed runs through Persistent=true.
What you will learn
- List the active timers and understand their state
- Tell a calendar timer (
OnCalendar) from a monotonic timer (OnBootSec) - Analyse an existing timer such as apt-daily.timer
- Create a custom timer with its associated service
- Enable persistence to catch up with missed runs
- Validate a calendar syntax with
systemd-analyze calendar
In which context?
systemd timers are gradually replacing cron on modern distributions:
- Run a daily backup with automatic logging
- Schedule a temporary files cleanup with fine control of the interval
- Fire a job 15 minutes after the system boots
- Make sure a missed job (machine powered off) is caught up at the next boot
- Use
journalctlto diagnose the errors of a scheduled job
The active timers
systemctl list-timers shows every timer known to systemd, with its next run and its last run. The --all option adds the inactive ones:
systemctl list-timers --allNEXT LEFT LAST PASSED UNIT ACTIVATESSat 2026-06-28 14:19:18 CEST 1h 28min Sat 2026-06-28 06:32:32 CEST 6h ago apt-daily.timer apt-daily.serviceSun 2026-06-29 00:00:00 CEST 11h Sat 2026-06-28 00:00:14 CEST 12h ago dpkg-db-backup.timer dpkg-db-backup.serviceSun 2026-06-29 00:23:41 CEST 11h Sat 2026-06-28 00:41:07 CEST 12h ago logrotate.timer logrotate.serviceSun 2026-06-29 06:19:57 CEST 17h Sat 2026-06-28 06:44:19 CEST 6h ago apt-daily-upgrade.timer apt-daily-upgrade.serviceThis sample output comes from a Debian: the lines are sorted by next due time, and the irregular times (00:23:41, 06:19:57) come from the random delay these timers add, explained below with RandomizedDelaySec. Each line shows:
| Column | Meaning |
|---|---|
| NEXT | Next planned run |
| LEFT | Time left before the next run |
| LAST | Last run |
| PASSED | Time elapsed since the last run |
| UNIT | Name of the timer |
| ACTIVATES | Service triggered |
Two kinds of timers
A timer fires either at a given date and time, or after a delay counted from a system event. Both forms can coexist in the same unit, systemd then keeping the closest due time.
Calendar timer (OnCalendar)
Fired at a precise moment, like cron. It uses the OnCalendar format:
[Timer]OnCalendar=*-*-* 03:00:00cron equivalent: 0 3 * * * (every day at 3:00).
Monotonic timer
Fired after a delay relative to an event (boot, last run, activation), whatever the wall-clock time:
[Timer]OnBootSec=15minOnUnitActiveSec=1hThis timer fires 15 minutes after boot, then every hour, counted from the last activation of its service.
| Directive | Fires relative to |
|---|---|
OnBootSec | System boot |
OnStartupSec | The start of systemd (user space) |
OnActiveSec | The activation of the timer itself |
OnUnitActiveSec | The last activation of the associated service |
OnUnitInactiveSec | The last deactivation of the associated service |
Analyse an existing timer
Take apt-daily.timer, present on Debian/Ubuntu, whose [Timer] section matches the one of the apt package on Debian 13 and Ubuntu 24.04:
systemctl cat apt-daily.timer[Unit]Description=Daily apt download activities
[Timer]OnCalendar=*-*-* 6,18:00RandomizedDelaySec=12hPersistent=true
[Install]WantedBy=timers.targetBreakdown
Each directive of this file answers a distinct need: when to fire, how to spread the load, and what to do after a power-off.
| Directive | Effect |
|---|---|
OnCalendar=*-*-* 6,18:00 | Fires at 6:00 and 18:00 every day |
RandomizedDelaySec=12h | Adds a random delay of up to 12 h to spread the load |
Persistent=true | If the run was missed (machine off), it is caught up at the next boot |
# Show the associated servicesystemctl cat apt-daily.serviceOnCalendar syntax
The OnCalendar syntax is more expressive than cron: it accepts ranges of days, seconds and named shortcuts, following this pattern:
DayOfWeek Year-Month-Day Hour:Minute:SecondCommon examples
Each expression below was passed to systemd-analyze calendar with systemd 257: the normalised form it returns confirms the reading given in the right-hand column. Note that weekly targets Monday at midnight, whereas cron's @weekly targets Sunday.
| Expression | Meaning |
|---|---|
*-*-* 03:00:00 | Every day at 3:00 |
Mon..Fri *-*-* 08:00:00 | Monday to Friday at 8:00 |
*-*-01 00:00:00 | The 1st of every month at midnight |
weekly | Every week (Monday 00:00) |
daily | Every day (00:00) |
hourly | Every hour |
*-*-* *:00/15:00 | Every 15 minutes |
Sat *-*-* 22:00:00 | Every Saturday at 22:00 |
Validate with systemd-analyze
systemd-analyze calendar checks the syntax and prints the next runs, without creating any unit:
systemd-analyze calendar 'Mon..Fri *-*-* 08:00' Original form: Mon..Fri *-*-* 08:00Normalized form: Mon..Fri *-*-* 08:00:00 Next elapse: Mon 2026-06-29 08:00:00 CEST (in UTC): Mon 2026-06-29 06:00:00 UTC From now: 19h left# Test a calendar with several occurrencessystemd-analyze calendar --iterations=5 'daily'Create a custom timer
Let us create a timer that cleans up /tmp every day at 4:00: a service carries the command, a timer carries the schedule.
-
Create the service
Terminal window sudo systemctl edit --full --force cleanup-tmp.service[Unit]Description=Cleanup of temporary files older than 7 days[Service]Type=oneshotExecStart=/usr/bin/find /tmp -type f -mtime +7 -delete -
Create the timer
Terminal window sudo systemctl edit --full --force cleanup-tmp.timer[Unit]Description=Daily /tmp cleanup timer[Timer]OnCalendar=*-*-* 04:00:00Persistent=true[Install]WantedBy=timers.target -
Enable and start the timer
systemctl editreloads the configuration itself; thedaemon-reloadonly becomes required when you create the files by hand in/etc/systemd/system/.Terminal window sudo systemctl daemon-reloadsudo systemctl enable --now cleanup-tmp.timer -
Check the timer
Terminal window systemctl status cleanup-tmp.timer● cleanup-tmp.timer - Daily /tmp cleanup timerLoaded: loaded (/etc/systemd/system/cleanup-tmp.timer; enabled)Active: active (waiting)Trigger: Sun 2026-06-29 04:00:00 CEST; 15h left -
Test the service by hand
Terminal window sudo systemctl start cleanup-tmp.servicesystemctl status cleanup-tmp.service
User timer (without sudo)
User timers live in ~/.config/systemd/user/ and need no root privileges. The %h specifier stands for the user's home directory, which avoids hard-coding its path:
mkdir -p ~/.config/systemd/user
# Create the servicecat > ~/.config/systemd/user/my-backup.service << 'EOF'[Unit]Description=Personal backup
[Service]Type=oneshotExecStart=%h/scripts/backup.shEOF
# Create the timercat > ~/.config/systemd/user/my-backup.timer << 'EOF'[Unit]Description=Personal backup timer
[Timer]OnCalendar=*-*-* 20:00:00Persistent=true
[Install]WantedBy=timers.targetEOF
# Enablesystemctl --user daemon-reloadsystemctl --user enable --now my-backup.timersystemctl --user list-timersAdvanced options
These three settings decide the actual behaviour of the timer on a machine that is not always on, or that must start several jobs at the same moment.
AccuracySec
By default, systemd groups timers within a one-minute window to save CPU wake-ups. AccuracySec controls this precision; 1s asks for a run accurate to the second:
[Timer]OnCalendar=*-*-* 03:00:00# Run accurate to the secondAccuracySec=1sThe comment sits on its own line, and it is not a matter of layout: a systemd unit accepts no end-of-line comment. Written AccuracySec=1s # precise, the setting is rejected then ignored, and systemd-analyze verify reports it with Failed to parse ... ignoring (measured with systemd 255 and 257). Hour-level precision, on the other hand, suits jobs that are in no hurry: the logrotate.timer of Ubuntu 24.04 carries AccuracySec=1h.
RandomizedDelaySec
Adds a random delay so that all machines do not start the same job simultaneously. Here, the run is spread over four hours after midnight:
[Timer]OnCalendar=daily# Spread over 4 hoursRandomizedDelaySec=4hThis is the choice made by Debian 13 and AlmaLinux 10 for their logrotate.timer, which carries RandomizedDelaySec=1h instead of Ubuntu's AccuracySec=1h: both avoid a wake-up at midnight sharp across a whole fleet.
Persistent
Persistent=true catches up with missed runs. If the machine was off at the planned time, the timer fires as soon as it is activated at the next boot. The systemd.timer(5) manual states that this setting only has an effect with OnCalendar: on a purely monotonic timer (OnBootSec, OnUnitActiveSec), it is accepted without a message but changes nothing.
[Timer]OnCalendar=dailyPersistent=truecron vs systemd timers
The table compares the two tools on the criteria that decide in practice: logging, catching up, precision and simplicity.
| Criterion | cron | systemd timers |
|---|---|---|
| Logging | syslog or file | journalctl -u service |
| Dependencies | None | After=, Requires= |
| Catch-up | No (partially with anacron) | Persistent=true |
| Precision | To the minute | To the second (with AccuracySec=1s) |
| Randomness | No | RandomizedDelaySec |
| Monotonic timer | No | OnBootSec, OnUnitActiveSec |
| Security (sandboxing) | No | All the systemd options |
| Simplicity | One line is enough | Two files (.timer + .service) |
| Universality | Available everywhere | Systems with systemd |
Troubleshooting
A timer involves two units, and the first question is always which one is at fault: did the timer not fire, or did the service fail once started? systemctl list-timers answers for the first (the LAST column), journalctl -u for the second.
| Symptom | Likely cause | Solution |
|---|---|---|
Timer missing from list-timers | Timer not enabled | systemctl enable --now my.timer |
enable --now fails, Loaded: bad-setting | Only OnCalendar line invalid | Fix the expression, test it with systemd-analyze calendar |
| One due time never fires, no error shown | A wrong OnCalendar line ignored next to a valid one | systemd-analyze verify on the .timer file |
Setting without effect (AccuracySec, RandomizedDelaySec...) | End-of-line comment, setting ignored | Put the comment on its own line, check with systemd-analyze verify |
| Service fails silently | Error in ExecStart | journalctl -u my-service.service |
| Timer fired up to one minute late | AccuracySec is 1 min by default | AccuracySec=1s if the second matters |
| Missed run not caught up | Persistent missing | Add Persistent=true |
| User timer inactive after reboot | Non-persistent session | loginctl enable-linger $USER |
# Diagnose a timersystemctl status my-job.timerjournalctl -u my-job.service --since today
# Check the next run of an expressionsystemd-analyze calendar '*-*-* 04:00:00'
# Detect the ignored settings in the filesystemd-analyze verify /etc/systemd/system/my-job.timer
# Force an immediate runsudo systemctl start my-job.servicePut it into practice
Create a systemd timer for a recurring job on a VM, with its service unit, then check that it fires and that it is persistent.
Key points
- A systemd timer = a
.timerfile + a.servicefile (same name by convention) - OnCalendar fires at a precise moment, monotonic timers (
OnBootSec,OnUnitActiveSec) after a delay - Persistent=true catches up with missed runs, always enable it for maintenance jobs
- systemd-analyze calendar checks the syntax before deploying
- Timers offer logging (
journalctl), sandboxing and dependencies, missing from cron - User timers live in
~/.config/systemd/user/;loginctl enable-lingermakes them run even without an open session - No end-of-line comment in a unit: the setting would be ignored,
systemd-analyze verifyreveals it - cron and timers coexist, choose according to complexity and logging needs
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Manage processes on Linux
A new full quiz with random questions
Work again only on the questions you missed