Make a Service Wait for a Linux Device with systemd.device
systemd.device turns a kernel device into a unit your services can depend on, so they wait for hardware instead of racing it.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide has you find the right unit name, wire a service to it with Requires= and After=, and prove the dependency actually works rather than just trusting the unit file. It uses systemd 255.4-1ubuntu8.17 on the machine where it was written. Give it fifteen minutes for a simple dependency, longer if you also need a udev rule for a device systemd does not expose on its own.
The example uses /dev/loop0 because it exists on this machine; swap in the device you actually care about. The demo service only checks that the node is a block device, then exits. Nothing here mounts, formats, writes to or removes the device.
1. Find the unit name systemd actually uses
Device units are generated on the fly. You never hand-write one, so do not go looking for a file. Start with a read-only check:
$ command -v systemctl
/usr/bin/systemctl
$ systemd-escape --path --suffix=device /dev/loop0
dev-loop0.device
$ systemctl show --no-pager --property=Id,ActiveState,Description dev-loop0.device
Id=dev-loop0.device
Description=/dev/loop0
ActiveState=active
/dev/loop0 becomes dev-loop0.device. The escaping matters more than it looks: a name like /dev/disk/by-id/... gets hyphens in specific places, so run it through systemd-escape rather than guessing them yourself. If the device is not present, systemctl show may say the unit cannot be found, or show it inactive.
Checkpoint
Keep the output of systemd-escape. That generated name is exactly what goes into Requires= and After= further down.
2. Know what a device unit can and cannot do
- No device-specific settings. A
.deviceunit has no[Device]section. Its configuration is the ordinary[Unit]and[Install]sections fromsystemd.unit, nothing more. - It tracks udev, not a file path. systemd generates device units for kernel devices carrying the
systemdudev tag: block and network devices by default, plus some others. The unit goes active when the device is considered plugged. - No default ordering. There are no built-in dependencies for device units. If a service needs to wait for one, you add
Requires=andAfter=yourself. - Presence, not persistence. A device unit describes the udev event, not a permanent file. A service can wait on it even if the device was completely absent when that service was first installed.
3. Write a service that requires the device
This step touches persistent system configuration and needs elevated privileges. Pick a real service name instead of device-check.service; the example below is deliberately harmless:
[Unit]
Description=Check that loop0 is available
Requires=dev-loop0.device
After=dev-loop0.device
[Service]
Type=oneshot
ExecStart=/usr/bin/test -b /dev/loop0
RemainAfterExit=yes
Save that as /etc/systemd/system/device-check.service with an editor run through sudo. Requires= means the service fails to start cleanly without the device unit. After= is purely about ordering: on its own it will not pull the device into the transaction or conjure a missing device into existence. Using both together is the usual, sane combination.
Skip ExecStart=/bin/sh -c ... for a test this simple. A direct executable skips the extra shell and one less place for quoting to go wrong. For a real service, swap the test for the actual program and keep its device access as narrow as you can.
4. Reload systemd and prove the dependency works
Any time you create or edit a unit file, tell systemd to reread it. This is elevated but non-destructive:
$ sudo systemctl daemon-reload
$ sudo systemctl start device-check.service
$ systemctl show --no-pager --property=Id,ActiveState,SubState,After device-check.service
Id=device-check.service
ActiveState=active
SubState=exited
Your real After= line will likely list more units than the trimmed output above. If the start fails, go straight to the service log:
$ systemctl status --no-pager device-check.service
$ journalctl -u device-check.service -n 20 --no-pager
Active and exited for a oneshot means the command ran successfully, and RemainAfterExit=yes is why systemd still calls it active. That proves the unit ran, not that a bigger application can genuinely use the device: test the real program separately.
Checkpoint
Confirm the dependency from systemd's own bookkeeping, not just by reading the unit file back:
$ systemctl show --no-pager --property=Requires,After device-check.service
Requires=dev-loop0.device ...
After=dev-loop0.device ...
5. Decide if this should survive a reboot
Started by hand, the example is fine for a first test. It will not run at boot, because it has no [Install] section. If the real service should come up with a normal boot target, add one:
[Install]
WantedBy=multi-user.target
Then, as an administrator:
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now device-check.service
$ systemctl is-enabled device-check.service
enabled
Enabling only creates a symlink in systemd's unit hierarchy; it does not switch a device on and it does not make the device permanent. If this was only a test, reverse it with sudo systemctl disable --now device-check.service. To remove the unit entirely, delete the file you created and run sudo systemctl daemon-reload again. That deletion cannot be undone, so keep a copy if the file holds real configuration.
6. Flip it round: let a device start a service
Sometimes you want the opposite relationship: a device showing up should want a service, not the other way round. A udev rule can add the systemd tag and set SYSTEMD_WANTS=. That property is read by the system service manager; SYSTEMD_USER_WANTS= is the equivalent for a user manager instance.
ACTION=="add", SUBSYSTEM=="block", KERNEL=="loop0", TAG+="systemd", ENV{SYSTEMD_WANTS}+="device-check.service"
Security boundary
This is a security-sensitive change. Match a stable property, such as an approved device identity, rather than a broad kernel name if the hardware can change. Put the rule under /etc/udev/rules.d/, validate its syntax with your distribution's udev tools, then reload and trigger only the device you mean to, following your own change process. Do not fire a broad trigger blind on a production host.
There is a timing trap worth knowing about: systemd acts on SYSTEMD_WANTS only when the device first goes active. Add the property after the device is already active, and the wanted unit will not retroactively start. A rule can also set SYSTEMD_READY=0 while a device is still incomplete, then flip it to 1 on a later changed event once it is genuinely ready. Do not lean on this mechanism unless you understand the device's udev event sequence first.
Common traps
- No device unit at all. Check the three layers separately: does the kernel device exist (
ls -l /dev/...and its sysfs path), is udev even running (systemctl is-active systemd-udevd.service), and does the generated name match what you expect (systemd-escape --path --suffix=device /dev/...)? - Containers. If
systemd-udevd.serviceis not running, there is no udev event source and no device units, full stop. This is normal in many containers. Adding more unit dependencies will not fix a missing event source; look at the container's device permissions and manager setup instead. - Present is not the same as ready. A device unit only tells systemd the udev device is plugged. It says nothing about an application protocol, a filesystem, firmware state or remote storage being usable. Put that readiness check inside the service or a small dedicated helper, and let it fail loudly rather than hiding the wait behind an unconditional sleep.
- Reload propagation is advanced, not a substitute. Device units reload when their device fires a udev
changedevent, and other units can react to that withReloadPropagatedFrom=. That is a specialised relationship, not a replacement for ordinaryRequires=orAfter=.
Done means
systemd-escapeproduced the device unit name from the real device path.- The dependent service uses
Requires=for presence andAfter=for ordering. daemon-reloadwas followed by an actual start test and a dependency check, not just a file review.- Boot enabling was a deliberate choice, and you know how to undo it with
disable --now. - You ruled out udev, readiness and container limits before blaming systemd.device itself.