Why does my systemd timer only trigger once when the unit is a target?
I have a couple of services (static site generators) that I want to trigger on a regular basis from the same systemd timer. I found this question/answer, which covers exactly what I want to do, and describes a setup whereby a .target
file that Wants=
multiple services is triggered by a corresponding timer. This sounds great, but I'm finding that when I actually set this up, it only ever triggers once, then disables itself!
I've prepared a minimal working example (this doesn't trigger multiple services, but demonstrates the same problem):
test-timer.timer
:
[Unit]
Description=A test timer
[Timer]
OnCalendar=*-*-* *:*:30
Unit=test-timer.target
[Install]
WantedBy=timers.target
test-timer.target
:
[Unit]
Description=Target unit
Wants=test-timer.service
After=test-timer.service
[Install]
WantedBy=timers.target
test-timer.service
:
[Unit]
Description=Run test
[Service]
ExecStart=/usr/bin/bash -c "date --rfc-3339='seconds' >> /tmp/test-timer-output"
[Install]
Also=test-timer.target
Enable the timer:
$ sudo cp test-timer.* /etc/systemd/system/
$ sudo systemctl enable --now test-timer.timer
Created symlink /etc/systemd/system/timers.target.wants/test-timer.timer → /etc/systemd/system/test-timer.timer.
Then, when I look at the output of systemctl list-timers --all
, prior to the first run I get (ignoring other timers):
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2021-10-08 10:38:30 EDT 21s left n/a n/a test-timer.timer test-timer.target
After the first run, NEXT
and LEFT
have been replaced with n/a
:
NEXT LEFT LAST PASSED UNIT ACTIVATES
n/a n/a Fri 2021-10-08 10:38:32 EDT 1min 5s ago test-timer.timer test-timer.target
I've also tried adding Persistent=true
to the test-timer.target
and explicitly enabling test-timer.target
, but neither of these work. Any time I do systemctl restart test-timer.timer
, it restarts, but only triggers one run, then never has another go.
If I remove the layer of indirection by changing the Unit=
line of test-timer.timer
to Unit=test-timer.service
, the service happily triggers itself every minute as expected.
Am I missing some configuration or installation step?
After getting some help on Twitter, I've managed to solve this issue. The problem is that a systemd timer will only activate services that are inactive, and the default behavior for a target is to activate and stay active unless something makes it go down (it is not tied to lifetime of the units it Wants=
). To force the target unit to become inactive if any of the services it activates becomes inactive, use BindsTo=
in place of Wants=
in my example above. So, for this minimal example:
test-timer.target
:
[Unit]
Description=Target unit
BindsTo=test-timer.service
After=test-timer.service
[Install]
WantedBy=timers.target
test-timer.service
:
[Unit]
Description=Run test
[Service]
ExecStart=/usr/bin/bash -c "date --rfc-3339='seconds' >> /tmp/test-timer-output"
[Install]
Also=test-timer.target
You can then see that as soon as test-timer.service
is finished running, test-timer.target
will also become inactive
(and thus the timer will be able to activate it again):
$ sudo systemctl list-units --all test-timer.target test-timer.service
UNIT LOAD ACTIVE SUB DESCRIPTION
test-timer.service loaded inactive dead Run test
test-timer.target loaded inactive dead Target unit
Whereas prior to the change, the target was staying active after the service died:
$ sudo systemctl list-units --all test-timer.target test-timer.service
UNIT LOAD ACTIVE SUB DESCRIPTION
test-timer.service loaded inactive dead Run test
test-timer.target loaded active active Target unit