A systemd timer runs scheduled maintenance, report generation, or short automation jobs without leaving recurring work split between shell loops, ad-hoc scripts, and old cron fragments. Keeping the schedule as a managed unit makes the job visible through systemctl and journalctl, which simplifies enablement, troubleshooting, and boot-time behavior.
A *.timer unit activates another unit at a wall-clock or monotonic interval. Current upstream systemd.timer documentation states that a timer starts the service with the same basename by default, while Unit= can point at a different unit when needed. Calendar timers created with OnCalendar= are also ordered after time-set.target and time-sync.target, and boot-enabled timers are normally attached through timers.target.
The example below creates a small oneshot service named systemd-timer-demo.service and a matching timer that fires at the start of each minute so the result can be verified quickly. System-level timers belong under /etc/systemd/system and need root access, repeating timers should not target services that stay active through RemainAfterExit=yes, and the example sets AccuracySec=1s because the upstream default is 1min, which can delay the visible run during a demo.
For a per-user timer, place both unit files under ~/.config/systemd/user and replace the sudo systemctl commands below with systemctl –user.
[Unit] Description=Append the current time to /var/log/systemd-timer-demo.log [Service] Type=oneshot ExecStart=/bin/sh -c 'date --iso-8601=seconds >> /var/log/systemd-timer-demo.log'
Timers commonly trigger short oneshot services that do one task and then exit. If the target service already exists, keep its real unit name and make the timer match that basename or add Unit= in the timer file.
Related: How to create a systemd service unit
[Unit] Description=Run systemd-timer-demo.service every minute [Timer] OnCalendar=*-*-* *:*:00 AccuracySec=1s Persistent=true [Install] WantedBy=timers.target
OnCalendar=*-*-* *:*:00 fires at the start of every minute. Replace that expression with the real schedule after the demo flow is understood.
AccuracySec=1s keeps the test run close to the scheduled second. Without that override, the upstream default AccuracySec=1min allows the manager to coalesce wake-ups inside a one-minute window.
Persistent=true catches up one missed run after downtime, but only for timers that use OnCalendar=.
$ sudo systemd-analyze verify /etc/systemd/system/systemd-timer-demo.service /etc/systemd/system/systemd-timer-demo.timer
No output is the ideal result. Current systemd-analyze verify behavior can also report warnings from some other loaded unit file, so read the filename on each message before assuming the new timer is wrong.
$ systemd-analyze calendar '*-*-* *:*:00'
Normalized form: *-*-* *:*:00
Next elapse: Mon 2026-04-13 21:40:00 +08
(in UTC): Mon 2026-04-13 13:40:00 UTC
From now: 12s left
systemd-analyze calendar is the safest way to confirm what OnCalendar= actually means before the unit starts firing on a real host.
$ sudo systemctl daemon-reload
This reloads the unit definitions from disk and rebuilds the dependency tree before enablement or manual starts.
$ sudo systemctl enable --now systemd-timer-demo.timer Created symlink /etc/systemd/system/timers.target.wants/systemd-timer-demo.timer → /etc/systemd/system/systemd-timer-demo.timer.
Enable the *.timer unit, not the triggered *.service unit. The service itself normally remains static and is started by the timer when the schedule elapses.
$ systemctl status --no-pager --full systemd-timer-demo.timer | sed -n '1,8p'
● systemd-timer-demo.timer - Run systemd-timer-demo.service every minute
Loaded: loaded (/etc/systemd/system/systemd-timer-demo.timer; enabled; preset: enabled)
Active: active (waiting) since Mon 2026-04-13 21:39:47 +08; 5ms ago
Trigger: Mon 2026-04-13 21:40:00 +08; 12s left
Triggers: ● systemd-timer-demo.service
Apr 13 21:39:47 host systemd[1]: Started systemd-timer-demo.timer - Run systemd-timer-demo.service every minute.
The timer success state is Loaded: loaded (…; enabled…) together with Active: active (waiting) and a future Trigger: timestamp.
$ systemctl list-timers --all systemd-timer-demo.timer --no-pager NEXT LEFT LAST PASSED UNIT ACTIVATES Mon 2026-04-13 21:40:00 +08 12s - - systemd-timer-demo.timer systemd-timer-demo.service 1 timers listed.
Before the first execution, LAST and PASSED stay empty. After the timer fires once, both columns show the most recent activation time.
$ cat /var/log/systemd-timer-demo.log
2026-04-13T21:40:00+08:00
$ systemctl status --no-pager --full systemd-timer-demo.service | sed -n '1,7p'
○ systemd-timer-demo.service - Append the current time to /var/log/systemd-timer-demo.log
Loaded: loaded (/etc/systemd/system/systemd-timer-demo.service; static)
Active: inactive (dead) since Mon 2026-04-13 21:40:00 +08; 56s ago
TriggeredBy: ● systemd-timer-demo.timer
Process: 2930 ExecStart=/bin/sh -c date --iso-8601=seconds >> /var/log/systemd-timer-demo.log (code=exited, status=0/SUCCESS)
Main PID: 2930 (code=exited, status=0/SUCCESS)
A successful oneshot service usually returns to inactive (dead) after it exits. The timestamp in the log file is the proof that the timer activated the service on schedule.
If the log file stays empty or the service status changes to failed, inspect the recent journal with sudo journalctl -u systemd-timer-demo.timer -u systemd-timer-demo.service -n 20 --no-pager.