Static addressing on a headless box without locking yourself out
2026-05-19
Network configuration is the one change where a typo costs you the connection you are using to fix the typo. The way through it is to know which layer you are editing and to always leave yourself a way back.
Which layer is actually in charge
On a current Ubuntu server the chain is: YAML in /etc/netplan/ → generated
.network file → systemd-networkd. When something behaves oddly,
look at the generated file rather than guessing from the YAML:
networkctl status ens18
ls /run/systemd/network/ /etc/systemd/network/
networkctl cat ens18 # the effective .network, after drop-ins
Two files defining the same interface is a real and common failure: a leftover
99-custom.yaml next to the installer's 50-cloud-init.yaml. netplan
merges them, and the result is rarely what either file meant on its own.
Making the change
# /etc/netplan/60-static.yaml
network:
version: 2
ethernets:
ens18:
dhcp4: false
addresses: [192.168.1.50/24]
routes:
- to: default
via: 192.168.1.1
- to: 192.168.1.99/32
via: 192.168.1.1
on-link: true
nameservers:
addresses: [192.168.1.1]
- Use
netplan try, notnetplan apply.tryapplies the configuration and rolls it back automatically if you do not confirm within the timeout. It is the difference between a scare and an outage. - Check the diff first:
netplan diffshows what would change in the generated units. on-link: trueis needed for a next-hop that is not derived from the interface's own prefix — without it networkd refuses the route and the failure is quiet.
Drop-ins instead of editing generated files
Anything netplan generates can be extended without fighting it, because networkd reads drop-in directories next to the unit:
/etc/systemd/network/10-netplan-ens18.network.d/extra.conf
[Route]
Destination=192.168.1.99/32
Gateway=192.168.1.1
This survives netplan apply and interface re-creation, which matters more
than it sounds: a route added with ip route at runtime belongs to nobody and is
lost the first time the interface is rebuilt — by a reboot, by a package upgrade that
restarts networkd, or by a DHCP renewal that reconfigures the link. Anything the machine
needs in order to come back must be declared, not typed.
Verifying
networkctl is-ready ens18
ip -4 addr show ens18
ip route show table all | grep -v 'linkdown'
resolvectl status | sed -n '/Current DNS Server/,+2p'
ping -c2 -W2 192.168.1.1
Then, and this is the part people skip: from a second session, confirm the box is reachable by the new address before closing the first one. Keep the old session open until you have typed something in the new one.
Boot-time ordering
Services that bind to an address will fail if they start before addressing exists.
systemd-networkd-wait-online is the blunt instrument; the precise version is to
depend on the interface being configured:
[Unit]
After=systemd-networkd.service
Wants=network-online.target
If a service instead retries and binds late, it will look healthy while silently having skipped whatever it does at startup. Check what it logged on boot, not whether it is running.