← Start of tutorial

WIUNIX ACADEMY

Reliable systemd timers: missed runs, repeatable jobs and completion intervals

Page 2 of 4. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Rocky Linux 10Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment3 editable values

Let the oneshot return to inactive after finishing

The service waits for the script to exit. Success returns it to inactive, so the next timer activation can start it again; leave RemainAfterExit unset. systemd allocates an unprivileged dynamic user and retains its private persistent state between starts. Do not create that state directory manually or assign its recycled numeric user ID to other files.

The two-minute startup timeout bounds this tiny fixture and can terminate it on expiry. For a real maintenance program, choose a timeout from its actual work and interruption behavior. A successful service exit is still only the program’s claim; check its meaningful output too.

ini · /etc/systemd/system/academy-daily-marker.service

File contents — not a shell command

[Unit]
Description=Academy daily marker job

[Service]
Type=oneshot
DynamicUser=yes
StateDirectory=academy-daily-marker
StateDirectoryMode=0700
ExecStart=/bin/sh /usr/local/libexec/academy-daily-marker.sh
TimeoutStartSec=2min

Schedule a daily UTC start with downtime catch-up

The timer requests one daily activation at 3:00 UTC, adds up to 300 seconds of random delay, and permits a further one-second accuracy window. Scheduler load can delay execution further. Random delay spreads a fleet’s starts; it does not reserve capacity or provide an SLA.

Persistent records the timer’s last trigger on disk. When the timer returns after inactivity, one or more missed occurrences cause one catch-up activation, still subject to the random delay. It does not replay every missed day and does not record whether the job succeeded. Enabling a timer with missed time can therefore start work soon; inspect that possibility before using this pattern for a real job.

ini · /etc/systemd/system/academy-daily-marker.timer

File contents — not a shell command

[Unit]
Description=Academy daily UTC schedule

[Timer]
OnCalendar=*-*-* 3:00:00 UTC
Persistent=yes
RandomizedDelaySec=300s
AccuracySec=1s
Unit=academy-daily-marker.service

[Install]
WantedBy=timers.target
04

LET’S BUILD

Validate the files and run the isolated job once

Check shell syntax and the installed systemd parser before loading the new files. Read every verifier message, including warnings about ignored settings. Then start only the fixture service and inspect its journal and marker directory; the timer remains disabled at this point.

A successful oneshot normally shows inactive after it finishes, Result=success and ExecMainStatus=0. The first run should log COMPLETED and create one dated text file. With DynamicUser, the public state path can be a symlink into systemd’s private state area; use authorized inspection, not ownership changes or a broad permission fix.

bash

Terminal on the authorized staging host

sudo /bin/sh -n '/usr/local/libexec/academy-daily-marker.sh' || exit 1
sudo systemd-analyze verify '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer' || exit 1
sudo systemctl daemon-reload || exit 1
sudo systemctl start 'academy-daily-marker.service' || exit 1
sudo systemctl show 'academy-daily-marker.service' -p ActiveState -p Result -p ExecMainStatus -p StateDirectory
sudo journalctl -u 'academy-daily-marker.service' --since '-5 min' --no-pager -n 40
sudo ls -l '/var/lib/academy-daily-marker/'
How do I know it worked?

The job exits successfully and its dated marker exists. A syntax pass or active timer alone is insufficient.

05

LET’S BUILD

Check repeatability, then enable only the timer

A second service start on the same UTC day should report ALREADY_DONE and preserve the single completed marker. After confirming that result, enable and start the timer. Enable the timer rather than the oneshot service: the schedule should pull in the service when needed.

Inspect NEXT and the effective properties. LAST or LastTriggerUSec refers to timer activation, not application completion; a manual service run does not establish a successful scheduled run. Keep the journal and the job’s own output as separate evidence. Persistent timestamps are bookkeeping, not a work queue.

bash

Terminal on the authorized staging host

sudo systemctl start 'academy-daily-marker.service' || exit 1
sudo journalctl -u 'academy-daily-marker.service' --since '-5 min' --no-pager -n 40
sudo systemctl enable --now 'academy-daily-marker.timer' || exit 1
systemctl is-enabled --quiet 'academy-daily-marker.timer' || exit 1
systemctl is-active --quiet 'academy-daily-marker.timer' || exit 1
systemctl list-timers --all 'academy-daily-marker.timer'
systemctl show 'academy-daily-marker.timer' -p ActiveState -p SubState -p LastTriggerUSec -p NextElapseUSecRealtime -p RandomizedDelayUSec -p AccuracyUSec
How do I know it worked?

The repeat start reports ALREADY_DONE. The timer is enabled and waiting with a plausible next occurrence.

Understand overlap, sleep and failed runs

If the same service is still active when a timer expires, systemd leaves that instance running rather than creating a second concurrent instance. This is local protection for this exact unit, not a lock against a second unit, a direct script run or another machine. Missed activations are not a durable backlog.

A calendar timer that becomes due while the host sleeps runs after resume; several sleep-time occurrences collapse into one activation. WakeSystem is off here, so this timer does not wake the machine. Monotonic timers normally pause their clock during suspend. These sleep semantics differ from catch-up after a timer was inactive.

Persistent does not retry a failed service until success. A later scheduled event may start it again; application retries need a bounded, deliberate policy. Avoid enabling an aggressive restart loop for a non-idempotent task. For long jobs, choose a completion-relative interval if the desired rule is a pause after finishing.

06

LET’S BUILD

Find why the schedule did not fire when expected

Compare the effective timer with its next occurrence, loaded source path and clock status. A disabled timer, wrong unit name, local-time expression, random delay or stale loaded configuration can explain an apparently missed start. Inspect before changing anything; do not erase the persistent timestamp to make the display look fresh.

If the next time is absent or unexpected, resolve the parser messages, any drop-ins and the actual clock. A timer shown as waiting says nothing about whether the prior job produced a valid result.

bash

Terminal on the authorized staging host

systemctl status 'academy-daily-marker.timer' --no-pager
systemctl cat 'academy-daily-marker.timer'
systemctl show 'academy-daily-marker.timer' -p FragmentPath -p DropInPaths -p ActiveState -p LastTriggerUSec -p NextElapseUSecRealtime
systemctl list-timers --all 'academy-daily-marker.timer'
timedatectl status
How do I know it worked?

The missing or delayed start has a concrete explanation in effective configuration or clock evidence; no schedule state has been changed.

Explore all tutorials ↗