Modern Linux server and desktop environments relying on systemd-networkd manage network interfaces, routing tables, and address assignments declaratively. Unlike traditional legacy scripting hooks found in /etc/network/if-up.d/, systemd-networkd runs as a native event-driven state machine. To execute custom automation scripts, configure dynamic firewall rules, update split-horizon DNS routing, or alert monitoring daemons when network interfaces change state, Ubuntu provides networkd-dispatcher.
The networkd-dispatcher daemon listens directly to D-Bus signals emitted by systemd-networkd and executes executable scripts located in state-specific trigger directories. This guide walks through configuring networkd-dispatcher on Ubuntu, creating robust hook scripts, parsing operational environment variables, and validating event execution.
Understanding networkd-dispatcher State Directories
Whenever an interface managed by systemd-networkd transitions through its operational lifecycle, networkd-dispatcher detects the state change and scans the corresponding directory under /etc/networkd-dispatcher/. The primary operational states and their directory paths include:
/etc/networkd-dispatcher/routable.d/: Executes when an interface acquires a valid address configuration and establishes default route connectivity to the network./etc/networkd-dispatcher/carrier.d/: Executes when physical or virtual link carrier detection succeeds (cable plugged in or virtual link active)./etc/networkd-dispatcher/degraded.d/: Executes when an interface has partial connectivity (for example, IPv4 configured but IPv6 routing unavailable)./etc/networkd-dispatcher/dormant.d/: Executes when the link is up but waiting for an external protocol action, such as 802.1X authentication./etc/networkd-dispatcher/off.d/: Executes when the interface is administratively brought down or detached./etc/networkd-dispatcher/configured.d/: Executes when static or dynamic link configuration completes before route calculation.
Before deploying hook scripts, inspect the daemon status and verify that networkd-dispatcher is active on the Ubuntu system:
systemctl status networkd-dispatcher.service
If the service is not currently active, enable and start it using systemctl:
sudo systemctl enable --now networkd-dispatcher.service
Inspecting State Transitions with networkctl
To understand which directory will be triggered when a network interface changes status, inspect the live operational state of system interfaces using networkctl:
networkctl status
For detailed information on a specific interface such as eth0 or ens3, run:
networkctl status ens3
Output details the operational state (such as routable, carrier, or degraded), DNS servers, and assigned addresses. When networkctl status reports routable, scripts in /etc/networkd-dispatcher/routable.d/ are dispatched automatically.
Exported Environment Variables in Hook Scripts
When networkd-dispatcher executes a script, it injects environment variables into the process execution context. Automation scripts can read these variables to ensure actions are applied only to target interfaces:
| Environment Variable | Description | Example Value |
|---|---|---|
$IFACE |
Name of the network interface transitioning state | ens3, eth0, wg0 |
$STATE |
Current operational state reported by systemd-networkd | routable, carrier, off |
$PREV_STATE |
The previous operational state prior to transition | carrier, degraded |
$AdministrativeState |
Administrative configuration status of the link | configured, unmanaged |
$OperationalState |
Underlying link protocol operational state | routable, dormant |
$IP_ADDRS |
Space-separated list of IPv4 and IPv6 addresses assigned | 192.168.1.50/24 fe80::1/64 |
Creating an Automated Hook Script for Routable Interfaces
Consider an enterprise requirement where dynamic policy routes or firewall rules must be reloaded whenever the primary upstream network interface transitions to routable. Create an executable script inside /etc/networkd-dispatcher/routable.d/:
sudo nano /etc/networkd-dispatcher/routable.d/50-update-custom-routes.sh
Paste the following script logic, which validates the interface name and logs state details to syslog using logger:
#!/usr/bin/env bash
set -euo pipefail
# Only apply actions to the primary uplink interface
TARGET_IFACE="ens3"
if [[ "${IFACE}" != "${TARGET_IFACE}" ]]; then
exit 0
fi
logger -t "networkd-dispatcher" "Interface ${IFACE} transitioned from ${PREV_STATE:-unknown} to ${STATE} with IP addresses: ${IP_ADDRS:-none}"
# Custom network actions (e.g. synchronise routes or trigger dynamic service reloads)
if [[ "${STATE}" == "routable" ]]; then
logger -t "networkd-dispatcher" "Applying route table synchronisation for ${IFACE}..."
# Example: ip route replace 10.200.0.0/16 via 192.168.1.1 dev "${IFACE}"
fi
exit 0
Setting Correct File Ownership and Execution Permissions
For security reasons, networkd-dispatcher ignores scripts that have permissive permissions, are owned by unprivileged users, or lack executable bits. Scripts must be owned by root:root with permissions set to 755:
sudo chown root:root /etc/networkd-dispatcher/routable.d/50-update-custom-routes.sh
sudo chmod 755 /etc/networkd-dispatcher/routable.d/50-update-custom-routes.sh
Confirm the permissions and ownership with ls -l:
ls -l /etc/networkd-dispatcher/routable.d/
Testing and Debugging Hook Execution
To verify that networkd-dispatcher triggers the hook properly without disconnecting remote SSH sessions, reconfigure or re-evaluate the target interface using networkctl:
sudo networkctl reconfigure ens3
Monitor the systemd journal logs for networkd-dispatcher and the custom syslog tag in real time:
journalctl -u networkd-dispatcher.service -t networkd-dispatcher -f
The journal will show the state transition detection and confirmation of the script execution:
networkd-dispatcher[1245]: Interface ens3 changed state to routable
networkd-dispatcher[1245]: Running /etc/networkd-dispatcher/routable.d/50-update-custom-routes.sh
networkd-dispatcher: Interface ens3 transitioned from carrier to routable with IP addresses: 192.168.1.50/24
networkd-dispatcher: Applying route table synchronisation for ens3...
If a script fails to execute, verify that the hashbang path (#!/usr/bin/env bash or #!/bin/sh) is valid, confirm that the script file contains no non-printable carriage return characters (CRLF), and ensure any external binaries invoked inside the hook specify absolute paths.