Run Actions on systemd-networkd State Changes
You will finish with a small, root-owned hook that records when an interface becomes routable, plus a way to verify the service, its environment and its startup behaviour. The examples use networkd-dispatcher 2.2.4-1, the package installed on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes. You need a system using systemd-networkd, a shell, and sudo access. The example writes to the system journal. It does not alter network configuration, but enabling the service can run startup triggers immediately, so read the script before installing it.
1. Check the installed contract
First confirm which executable and package version you are using. These are ordinary, read-only commands and do not need elevated privileges:
$ command -v networkd-dispatcher
/usr/bin/networkd-dispatcher
$ dpkg-query -W -f='${Package} ${Version}\n' networkd-dispatcher
networkd-dispatcher 2.2.4-1
$ networkd-dispatcher --help
usage: networkd-dispatcher [-h] [-S SCRIPT_DIR] [-T] [-v] [-q]
The daemon listens for systemd-networkd signals over D-Bus. It does not poll for changes. Its default script search path is /etc/networkd-dispatcher:/usr/lib/networkd-dispatcher. The first directory has precedence when scripts with the same name exist in both locations, which makes /etc the suitable place for local hooks.
Checkpoint
If the version or path is different on your host, keep the local help output as the authority for command-line details.
2. Choose a state directory
Each hook belongs in a directory named for the destination state. The available directories are routable.d, dormant.d, no-carrier.d, off.d, carrier.d, degraded.d, configured.d and configuring.d. A routable hook is a useful example because it runs when networkd reports that an interface has usable routing.
These directories are not a general-purpose event bus. A script in routable.d is selected by the state event, not by an arbitrary file-system change. The state names come from systemd-networkd and are separate from administrative states such as failed or unmanaged.
Create the local directory if necessary. This changes only the directory structure and requires elevated privileges:
$ sudo install -d -o root -g root -m 0755 /etc/networkd-dispatcher/routable.d
$ stat -c '%U:%G %a %n' /etc/networkd-dispatcher/routable.d
root:root 755 /etc/networkd-dispatcher/routable.d
3. Install a harmless diagnostic hook
Write a script that records the interface, destination state and address variables. It uses logger, so it does not need to create its own log file or manage permissions:
$ cat > /tmp/50-networkd-dispatcher-example <<'EOF'
#!/bin/sh
set -eu
/usr/bin/logger -t networkd-dispatcher \
"iface=${IFACE:-unknown} state=${STATE:-unknown} ipv4=${ADDR:-none}"
EOF
$ sudo install -o root -g root -m 0755 \
/tmp/50-networkd-dispatcher-example \
/etc/networkd-dispatcher/routable.d/50-example
$ stat -c '%U:%G %a %n' /etc/networkd-dispatcher/routable.d/50-example
root:root 755 /etc/networkd-dispatcher/routable.d/50-example
The explicit owner, group and mode matter. The daemon is intended to run system-wide as root, and the installed project documentation says it ignores scripts that are not executable and root-owned. The :- forms keep the log useful if an event does not provide a particular variable. IP_ADDRS and IP6_ADDRS can contain several space-delimited addresses, so do not treat either as one guaranteed address.
Scripts in one state directory run in alphanumeric filename order. A prefix such as 50- gives you a predictable place among other local hooks. Keep hooks short and make them safe to repeat: a state transition can happen again after a reconnect or restart.
4. Inspect the service before starting it
The package supplies networkd-dispatcher.service. Inspect its command and the optional environment file before making a service change:
$ systemctl cat networkd-dispatcher.service
[Service]
Type=notify
ExecStart=/usr/bin/networkd-dispatcher $networkd_dispatcher_args
EnvironmentFile=-/etc/default/%p
$ sed -n '1,80p' /etc/default/networkd-dispatcher
networkd_dispatcher_args="--run-startup-triggers"
--run-startup-triggers generates events for state already present when the daemon starts. That helps avoid missing an interface that became active before the dispatcher, but it also means the new hook may run as soon as the service starts. Treat this as a service-disrupting boundary for any hook that restarts daemons, changes routes or starts a VPN. The example only writes a journal message.
5. Start and verify the dispatcher
Starting the service is an elevated operation. It may execute the new hook because of the configured startup trigger:
$ sudo systemctl enable --now networkd-dispatcher.service
$ systemctl is-enabled networkd-dispatcher.service
enabled
$ systemctl is-active networkd-dispatcher.service
active
$ systemctl status --no-pager networkd-dispatcher.service
The status output should show an active service. Its exact main process and timestamps vary. If it is not active, read the service log before changing the script:
$ journalctl -u networkd-dispatcher.service -b --no-pager
$ journalctl -t networkd-dispatcher -b --no-pager
Sep 25 10:15:00 host networkd-dispatcher[1234]: iface=enp1s0 state=routable ipv4=192.0.2.10
The timestamp, host, PID and interface are examples. The useful evidence is a dispatcher service without a start failure and a log line from the hook. If your current interface is not becoming routable, do not force a network change just to produce a test event. Check the state with networkctl status and wait for a normal reconnect or boot.
6. Pass options only when you need them
The service reads extra arguments from /etc/default/networkd-dispatcher. The command-line options are small in scope:
-Tor--run-startup-triggersemits events for pre-existing state.-S SCRIPT_DIRor--script-dir SCRIPT_DIRreplaces the script search path; separate multiple directories with a colon.-vraises logging verbosity by one level.-qlowers it by one level.
Do not add -S merely to find a missing hook. It changes where every hook is searched for and can hide the packaged directories. First check the spelling, ownership, executable bit and state directory. If you edit the defaults file, restart the service during a maintenance window and inspect systemctl cat again.
7. Remove or roll back the example
To undo this guide, remove only the hook you created, then restart the service if you want to clear its in-memory view. The removal requires elevated privileges and stops future invocations of this hook:
$ sudo rm -- /etc/networkd-dispatcher/routable.d/50-example
$ sudo systemctl restart networkd-dispatcher.service
$ test ! -e /etc/networkd-dispatcher/routable.d/50-example && echo 'example hook removed'
example hook removed
Stopping and disabling the whole service is a wider rollback. Do that only if you own the service's other hooks:
$ sudo systemctl disable --now networkd-dispatcher.service
That command prevents all networkd-dispatcher hooks from running, including package-provided or other local actions. Re-enable it with sudo systemctl enable --now networkd-dispatcher.service after checking the configuration.
Done means
- The installed version and help output were checked.
- The hook is in the state directory that matches the event you intend to handle.
- The hook is executable and owned by root, with a predictable filename.
- The service is active and the journal shows the hook's diagnostic message, when a matching event has occurred.
- You understand that startup triggers can run a hook immediately when the service starts.
- You can remove the example without disabling unrelated network actions.