Illustrative people-free WIUNIX Academy operations workspace with a systemd scheduling board showing missed-run catch-up and completion intervals

WIUNIX ACADEMY

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

Build an isolated repeatable oneshot, inspect UTC calendar catch-up, and choose a completion-relative interval when a daily deadline is the wrong fit.

Intermediate to advanced35 min readUpdated
Make this guide yours

YOUR SYSTEM. YOUR STEPS.

Where are you working?

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

AlmaLinux 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

THE OUTCOME

What will you have at the end?

Build an isolated repeatable oneshot, inspect UTC calendar catch-up, and choose a completion-relative interval when a daily deadline is the wrong fit.

Supported operating systems and versions

Covers Ubuntu 26.10/26.04 LTS/24.04 LTS, Debian 13/12, RHEL/Rocky/AlmaLinux 10/9 and rolling Arch/Manjaro. Environment paths distinguish installed-package queries and mandatory-access-control diagnostics. Rolling paths are dated reviews with installed-build checks. These are documented paths, not an all-OS execution claim; a local timer is neither a durable queue nor an application idempotency guarantee.

Before you begin

  • A staging Linux host with systemd as PID 1, authorized sudo access and a working sudoedit editor.
  • An unused fixture name and a little local state space; existing units and application schedules must not be replaced.
  • Familiarity with systemd units, process exit codes and journal inspection; start/stop actions target only this fixture.

Choose the promise your schedule can keep

A daily maintenance task can disappear during downtime, run twice after a retry, or still be running when its next deadline arrives. A timer answers when to request a service start; the job must decide whether work is needed and whether a previous attempt committed its result.

We build a new, isolated daily marker job, inspect its result, and then compare a UTC wall-clock schedule with an interval measured after completion. The marker writes only its own tiny files. It is not a backup, billing system or exactly-once transaction example. Use a staging host; do not replace an existing production schedule with this fixture.

A calendar schedule suits a particular hour. A completion-relative interval suits polling or maintenance that should rest after each run. A machine-local timer does not coordinate a cluster; use an application queue or distributed coordinator if every missed business event must be processed.

Set the public example values before copying

Open the example-values form, choose a new job name and a UTC hour from 0 to 23, then apply the values. The delay spreads starts by up to 300 seconds; it is not a deadline guarantee. The job name also chooses the unit files, script and managed state directory, so never reuse an existing application name.

Values stay in this tab for the selected tutorial and environment. Pending or invalid edits block copying until Apply or Reset. These fields accept public examples only: no passwords, tokens or private application arguments. The daily marker always uses the UTC date; changing the hour does not change its timezone.

01

LET’S BUILD

Check this distribution and its installed tools

Use a currently maintained minor release of RHEL, Rocky Linux or AlmaLinux 10 or 9. Resolve missing tools through entitled RHEL repositories or the corresponding official distribution repositories. The installed systemd package is the launcher and service manager; no cron daemon, external repository or replacement systemd is needed.

The common files go into the administrator unit directory, not vendor package unit files. Keep SELinux enforcing. The script label, Unix permissions and dynamic-user state access can each affect execution independently of timer activation. Query the package build instead of assuming all 9/10-family hosts have the same systemd version.

bash

Terminal on the authorized staging host

cat /etc/os-release
cat /proc/1/comm
systemd --version
rpm -q systemd coreutils sudo audit policycoreutils libselinux-utils
command -v systemctl systemd-analyze journalctl timedatectl sudo sudoedit date mktemp mv
command -v getenforce ausearch restorecon
How do I know it worked?

PID 1 is systemd and all required tools are available; resolve a mismatch before continuing.

02

LET’S BUILD

Read the clock and preview the next calendar times

The schedule uses UTC explicitly, so a change to the host’s local timezone or daylight-saving rules does not shift the intended UTC hour. Inspect synchronization and ask systemd to calculate upcoming times before saving a timer. A valid calendar expression proves syntax, not that NTP, hardware wake-up or the application is healthy.

Keep the host’s established time service. Ordering after time-sync.target alone does not prove the clock is accurate; correlate the status with that service’s own health. Do not change the host timezone or jump its clock to rehearse catch-up.

bash

Terminal on the authorized staging host

timedatectl status
date -u
systemd-analyze calendar --iterations=5 '*-*-* 3:00:00 UTC'
How do I know it worked?

The normalized expression and five upcoming UTC occurrences match the intended schedule; investigate an unsynchronized clock before activation.

03

LET’S BUILD

Reserve an unused job and open its three files

The fixture gets its own script, service and timer. Refuse an existing unit or state directory, including the private directory used by DynamicUser. The exclusive file creation also refuses symlinks and existing files; if a later reservation fails, earlier empty files can remain. Inspect those exact files and keep the timer disabled while correcting the reservation.

Paste the following three file panels into their matching editor buffers. The shared script directory may already exist; only the named files belong to this lesson. Do not change any vendor unit or another task’s files. Administrator rights are needed to install files, but the job itself runs with a temporary unprivileged identity.

bash

Terminal on the authorized staging host

test "$(systemctl show -p LoadState --value 'academy-daily-marker.service')" = not-found || exit 1
test "$(systemctl show -p LoadState --value 'academy-daily-marker.timer')" = not-found || exit 1
sudo test ! -e '/var/lib/academy-daily-marker' || exit 1
sudo test ! -e '/var/lib/private/academy-daily-marker' || exit 1
sudo install -d -m 0755 /usr/local/libexec
sudo sh -c 'set -euC; : > "$1"; : > "$2"; : > "$3"' sh '/usr/local/libexec/academy-daily-marker.sh' '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer' || exit 1
sudo chmod 0644 '/usr/local/libexec/academy-daily-marker.sh' '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer'
sudoedit '/usr/local/libexec/academy-daily-marker.sh' '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer'
How do I know it worked?

Three newly reserved, root-owned files are open. A collision stops this step without replacing an existing file.

Write a repeatable job with an atomic final marker

The job uses the state directory supplied by systemd, derives today’s UTC date, and returns success if that day’s marker already exists. Otherwise it writes a temporary file in the same directory and renames it into place. A normal interruption before the rename leaves no completed marker; an abrupt power loss can leave a temporary file.

This illustrates repeatability for one small local artifact. The rename does not make an external API call, database update or backup exactly once, and it is not a disk-flush durability protocol. A real job needs its own transaction, idempotency key or durable checkpoint. Start this script only through this service; a direct parallel invocation bypasses systemd’s serialization.

sh · /usr/local/libexec/academy-daily-marker.sh

File contents — not a shell command

#!/bin/sh
set -eu
umask 077
: "${STATE_DIRECTORY:?Start this job through its systemd service}"
day=$(date -u +%F)
report="$STATE_DIRECTORY/$day.txt"
if test -f "$report"; then
    printf 'ALREADY_DONE %s\n' "$day"
    exit 0
fi
tmp=$(mktemp "$STATE_DIRECTORY/.report.XXXXXX")
trap 'rm -f -- "$tmp"' EXIT HUP INT TERM
printf 'academy daily marker for UTC day %s\n' "$day" > "$tmp"
mv -- "$tmp" "$report"
trap - EXIT HUP INT TERM
printf 'COMPLETED %s\n' "$day"
Explore all tutorials ↗