in this post we will look at how systemd handles dependencies and how they can be used to increase the robustness of the system, and also discuss potential pitfalls.
For the existing services all of the examples should be put in
/etc/systemd/system/<service you change>.d/<override>.conf,
as this way you will only override the things you change in unit, instead of editing whole unit file in
(and having your changes overridden on package upgrade).
Unless mentioned otherwise the dependencies are specified in
Types of dependencies
All of them accept space separated list of units.
Unit "wanting" dependency makes systemd try to run the specified dependency when you start the unit. Note that doesn't mean the dependency needs to be successful, if wanted dependency fails the unit will still run.
It also doesn't impose order, just the fact it must be running, which makes it probably most confusing one of the list.
Wants, with caveat that it should be added in the
[Install] section as opposed to '[Unit]` section
Is the stronger version of
Wants. Failure to run one will fail your unit but it still doesn't imply
It will also make your unit stop if the dependency is stopped.
Note that there is a bit of race condition here, as just using
Require won't impose the order and won't make
your unit wait for the required unit to start (and possibly fail) so without explicit
After it might still
run if the dependent unit failed.
For that reason most of the unit files have both
After on units they depend on
Requires that doesn't automatically start required services and fails if they are not running at the moment
It is continuous version of
Wants. Any unit specified there will be restarted any time it is found not running.
Two dependencies that conflict eachother will not error out when trying to run any of them. That also doesn't imply ordering.
Instead, the old one will be stopped and the new one will be started at the same time.
Space separated list of filesystem paths needed for unit to work. Any mounts on that path will be added as both
This is probably most useful in context of databases (especially if said database tries to init empty DB when faced with empty dir), as you can use it to easily make "do not start DB if volume it is using didn't mount correctly" dependency with it.
noauto will not be included in this which is a bit of a trap.
Configure ordering between the units. Adding that dependency will make systemd fully start one unit before activating one dependant on it, instead of trying to do it all at once, and reverse of that on shutdown
Devices have no working
Before as they are created by hotplug events, not activated
As name implies, reload of this unit will propagate reload to other units. The link is in one direction
As name implies, stop of this unit will propagate stop to other units. The link is in one direction
On top of dependencies that
Requires would provide,
BindsTo make it so unit will be stopped if the bound unit is stopped.
In most cases you also want to add
After to the same dependency to make sure the unit is also started after the service it is bound to is activated.
This is pretty hard dependency as even if service will be stopped unexpectedly (say the other one crashes), the bound service will also be stopped
Literally "hold that service up". Services defined in
Upholds will be kept running (and restarted in case they fail) as long as service that defined it is kept running.
This only happens for starting the service, the services won't be stopped if you stop the unit that specified
It might be a good way to keep services your service depends on running without having to modify each one with
one caveat is that this can be nontrivial to debug for ops people that are unfamiliart with gritty details of systemd so use it with care.
This is probably a reason why it is very rarely used in distros.
PartOf "leeches" any stop and restart event of the unit it is part of.
Main usage is making sure few units that always should work together will also be restarted together
Normally systemd will not stop units unless they collide with other units.
StopWhenUnneeded=true will actively garbage collect services that are not depended on by anything else.
Can be useful if you have service that's only useful to other services, for example a lot of distros have bluetooth service using that option.
Examples of usage
Start database only if the filesystem it uses is mounted
There are 2 ways of doing it. The "generic" way would be just pointing out the unit DB needs to start:
1[Unit] 2Requires=var-lib.mount 3After=var-lib.mount
The more specific way would be using
1[Unit] 2RequiresMountsFor=/var/lib/mysql /etc/mysql/
And some packages modern distros do exactly that. From Debian:
1ᛯ cat /lib/systemd/system/postgresql@.service |grep Mounts 2RequiresMountsFor=/etc/postgresql/%I /var/lib/postgresql/%I
(sadly mariadb package doesn't do that, at least at this moment)
Keeping flaky service you depend on running
The standard way would be to just add restart/restart sec to the service that is flaky
1... 2[Service] 3ExecStart=/usr/bin/flaky-service 4Restart=always, 5RestartSec=10
and then using normal dependencies on service that is depending on it
1[Unit] 2BindsTo=flaky.service 3After=flaky.service
Alternative is to just specify
but that can be annoying to debug if someone tries to stop the
flaky.service manually and it just gets instantly restarted with apparently no reason.
Being explicit here is usually a better way.
Running service when the given device is mounted.
Let's assume for a second we want to run a backup script or a sync daemon every time external drive is connected.
First, we set up our service
1[Unit] 2Description=run sync service if external drive is connected 3# We want it after mount is mounted 4After=mnt-data.mount 5# We want it to stop when the mount is unmounted 6BindsTo=mnt-data.mount 7[Service] 8ExecStart=/usr/local/bin/sync-script 9... # whatever other options the service needs 10 11[Install] 12WantedBy=multi-user.target
then, we modify mount to trigger it:
and reload systemd via
1 (!) [15:09:39]:/lib/systemd/system32☠ mount /mnt/data 2 (!) [15:09:42]:/lib/systemd/system☠ systemctl status run-sync 3● run-sync.service - run sync after mount 4 Loaded: loaded (/lib/systemd/system/run-sync.service; enabled; vendor preset: enabled) 5 Active: active (running) since Sun 2022-02-27 15:09:42 CET; 2s ago 6 Main PID: 189014 (sync-script) 7 Tasks: 2 (limit: 38330) 8 Memory: 576.0K 9 CPU: 5ms 10 CGroup: /system.slice/run-sync.service 11 ├─189014 /bin/bash /usr/local/bin/sync-script 12 └─189016 sleep 60m 13 14Feb 27 15:09:42 hydra systemd: Started run sync after mount. 15 16 (!) [15:09:47]:/lib/systemd/system☠ umount /mnt/data 17 (!) [15:09:49]:/lib/systemd/system☠ st run-sync 18○ run-sync.service - run sync after mount 19 Loaded: loaded (/lib/systemd/system/run-sync.service; enabled; vendor preset: enabled) 20 Active: inactive (dead) since Sun 2022-02-27 15:09:49 CET; 855ms ago 21 Process: 189014 ExecStart=/usr/local/bin/sync-script (code=killed, signal=TERM) 22 Main PID: 189014 (code=killed, signal=TERM) 23 CPU: 5ms
service gets stopped when the mount is unmounted.
Caveat with mounts.
mount is run independently of systemd that can then only take action on that. Which means if your service have actively open files,
umount /mnt/data will fail.
Workaround for it is to tell systemd to stop the mount via
systemctl stop /mnt/music, or to have script itself only be active from time to time (say syncing every hour), then hope you won't hit that.
Mount is just not integrated with systemd.