Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Schedule tasks with systemd timers on Linux

By updated

Improve this page

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 journalctl to 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:

Terminal window
systemctl list-timers --all
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sat 2026-06-28 14:19:18 CEST 1h 28min Sat 2026-06-28 06:32:32 CEST 6h ago apt-daily.timer apt-daily.service
Sun 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.service
Sun 2026-06-29 00:23:41 CEST 11h Sat 2026-06-28 00:41:07 CEST 12h ago logrotate.timer logrotate.service
Sun 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.service

This 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:

ColumnMeaning
NEXTNext planned run
LEFTTime left before the next run
LASTLast run
PASSEDTime elapsed since the last run
UNITName of the timer
ACTIVATESService 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:00

cron 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=15min
OnUnitActiveSec=1h

This timer fires 15 minutes after boot, then every hour, counted from the last activation of its service.

DirectiveFires relative to
OnBootSecSystem boot
OnStartupSecThe start of systemd (user space)
OnActiveSecThe activation of the timer itself
OnUnitActiveSecThe last activation of the associated service
OnUnitInactiveSecThe 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:

Terminal window
systemctl cat apt-daily.timer
[Unit]
Description=Daily apt download activities
[Timer]
OnCalendar=*-*-* 6,18:00
RandomizedDelaySec=12h
Persistent=true
[Install]
WantedBy=timers.target

Breakdown

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.

DirectiveEffect
OnCalendar=*-*-* 6,18:00Fires at 6:00 and 18:00 every day
RandomizedDelaySec=12hAdds a random delay of up to 12 h to spread the load
Persistent=trueIf the run was missed (machine off), it is caught up at the next boot
Terminal window
# Show the associated service
systemctl cat apt-daily.service

OnCalendar 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:Second

Common 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.

ExpressionMeaning
*-*-* 03:00:00Every day at 3:00
Mon..Fri *-*-* 08:00:00Monday to Friday at 8:00
*-*-01 00:00:00The 1st of every month at midnight
weeklyEvery week (Monday 00:00)
dailyEvery day (00:00)
hourlyEvery hour
*-*-* *:00/15:00Every 15 minutes
Sat *-*-* 22:00:00Every Saturday at 22:00

Validate with systemd-analyze

systemd-analyze calendar checks the syntax and prints the next runs, without creating any unit:

Terminal window
systemd-analyze calendar 'Mon..Fri *-*-* 08:00'
Original form: Mon..Fri *-*-* 08:00
Normalized 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
Terminal window
# Test a calendar with several occurrences
systemd-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.

  1. 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=oneshot
    ExecStart=/usr/bin/find /tmp -type f -mtime +7 -delete
  2. Create the timer

    Terminal window
    sudo systemctl edit --full --force cleanup-tmp.timer
    [Unit]
    Description=Daily /tmp cleanup timer
    [Timer]
    OnCalendar=*-*-* 04:00:00
    Persistent=true
    [Install]
    WantedBy=timers.target
  3. Enable and start the timer

    systemctl edit reloads the configuration itself; the daemon-reload only becomes required when you create the files by hand in /etc/systemd/system/.

    Terminal window
    sudo systemctl daemon-reload
    sudo systemctl enable --now cleanup-tmp.timer
  4. Check the timer

    Terminal window
    systemctl status cleanup-tmp.timer
    ● cleanup-tmp.timer - Daily /tmp cleanup timer
    Loaded: loaded (/etc/systemd/system/cleanup-tmp.timer; enabled)
    Active: active (waiting)
    Trigger: Sun 2026-06-29 04:00:00 CEST; 15h left
  5. Test the service by hand

    Terminal window
    sudo systemctl start cleanup-tmp.service
    systemctl 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:

Terminal window
mkdir -p ~/.config/systemd/user
# Create the service
cat > ~/.config/systemd/user/my-backup.service << 'EOF'
[Unit]
Description=Personal backup
[Service]
Type=oneshot
ExecStart=%h/scripts/backup.sh
EOF
# Create the timer
cat > ~/.config/systemd/user/my-backup.timer << 'EOF'
[Unit]
Description=Personal backup timer
[Timer]
OnCalendar=*-*-* 20:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Enable
systemctl --user daemon-reload
systemctl --user enable --now my-backup.timer
systemctl --user list-timers

Advanced 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 second
AccuracySec=1s

The 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 hours
RandomizedDelaySec=4h

This 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=daily
Persistent=true

cron vs systemd timers

The table compares the two tools on the criteria that decide in practice: logging, catching up, precision and simplicity.

Criterioncronsystemd timers
Loggingsyslog or filejournalctl -u service
DependenciesNoneAfter=, Requires=
Catch-upNo (partially with anacron)Persistent=true
PrecisionTo the minuteTo the second (with AccuracySec=1s)
RandomnessNoRandomizedDelaySec
Monotonic timerNoOnBootSec, OnUnitActiveSec
Security (sandboxing)NoAll the systemd options
SimplicityOne line is enoughTwo files (.timer + .service)
UniversalityAvailable everywhereSystems 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.

SymptomLikely causeSolution
Timer missing from list-timersTimer not enabledsystemctl enable --now my.timer
enable --now fails, Loaded: bad-settingOnly OnCalendar line invalidFix the expression, test it with systemd-analyze calendar
One due time never fires, no error shownA wrong OnCalendar line ignored next to a valid onesystemd-analyze verify on the .timer file
Setting without effect (AccuracySec, RandomizedDelaySec...)End-of-line comment, setting ignoredPut the comment on its own line, check with systemd-analyze verify
Service fails silentlyError in ExecStartjournalctl -u my-service.service
Timer fired up to one minute lateAccuracySec is 1 min by defaultAccuracySec=1s if the second matters
Missed run not caught upPersistent missingAdd Persistent=true
User timer inactive after rebootNon-persistent sessionloginctl enable-linger $USER
Terminal window
# Diagnose a timer
systemctl status my-job.timer
journalctl -u my-job.service --since today
# Check the next run of an expression
systemd-analyze calendar '*-*-* 04:00:00'
# Detect the ignored settings in the file
systemd-analyze verify /etc/systemd/system/my-job.timer
# Force an immediate run
sudo systemctl start my-job.service

Put 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 .timer file + a .service file (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-linger makes them run even without an open session
  • No end-of-line comment in a unit: the setting would be ignored, systemd-analyze verify reveals 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

6 questions
6 min
70% to pass

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

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn