Setting up a static IP address on Debian and Ubuntu
Fixed IPv4 and IPv6 addresses on Ubuntu 24.04, Ubuntu 22.04, Debian 13 and Debian 12: netplan, ifupdown and systemd-networkd compared, with a rollback strategy and extra IPs.
Four values you should never start without
A static address is written down in a minute. The reason so many servers still go silent after a reboot is almost never the syntax, it is that one of the four basic values was guessed instead of read off the running system: the interface name, the address including its prefix length, the gateway and the nameservers. Read these values while the machine is still up and reachable.
ip -brief address show
ip -4 route show
ip -6 route show
cat /etc/resolv.conf
The first line gives you the interface name. On virtual servers it is called ens3, enp1s0 or eth0, depending on the platform, and on bare metal often eno1. Copy the name, do not guess it. A typo at this point produces a syntactically flawless configuration that simply does not match any interface that exists.
The prefix length deserves particular attention. Many providers route a single IPv4 address to the server as a /32, which puts the gateway outside your own subnet. Others hand out classic /24 networks. The path actually in use tells you which case you are dealing with:
ip route get 1.1.1.1
After that, make a backup. It costs ten seconds and it is the difference between a rollback and a support ticket.
cp -a /etc/netplan /root/netplan.bak
cp -a /etc/network/interfaces /root/interfaces.bak
Which system is managing your network right now?
Debian and Ubuntu use different tools, and the most common total outage happens when two of them configure the same interface at the same time. Get a clear picture first:
ls -l /etc/netplan/
ls -l /etc/network/interfaces /etc/network/interfaces.d/
ls -l /etc/systemd/network/
The rule of thumb for the four current releases: Ubuntu 24.04 and Ubuntu 22.04 are configured through netplan, which drives systemd-networkd in the background. Debian 13 and Debian 12 come out of a standard installation with ifupdown and the file /etc/network/interfaces. systemd-networkd is present on Debian but not active, and netplan can be installed there, although it is not the intended path.
Cloud images add another layer: on first boot, cloud-init writes a file of its own, typically /etc/netplan/50-cloud-init.yaml, or on Debian a block in /etc/network/interfaces.d/. If you edit that file without holding cloud-init back, the old configuration is waiting for you again after the next reboot.
Ubuntu 24.04 and 22.04: a static address with netplan
netplan reads every file in /etc/netplan/ in alphabetical order and translates them into configuration for systemd-networkd. Do not create a second file for the same interface. Edit the existing one instead, or disable the old one properly. A complete configuration with IPv4 and IPv6 looks like this:
network:
version: 2
renderer: networkd
ethernets:
ens3:
dhcp4: false
dhcp6: false
accept-ra: false
addresses:
- 203.0.113.10/24
- "2001:db8:1234::2/64"
routes:
- to: default
via: 203.0.113.1
- to: default
via: "2001:db8:1234::1"
nameservers:
addresses: [9.9.9.9, 149.112.112.112, 2620:fe::fe]
Three details in there are worth explaining one by one.
First, the default routes live under routes and no longer under gateway4 or gateway6. Both keys have been deprecated since netplan 0.103. They still work on Ubuntu 22.04 and 24.04, but every invocation answers with `gateway4` has been deprecated, use default routes instead. If you are writing a configuration from scratch, write routes.
Second, accept-ra: false switches off automatic IPv6 configuration. If you leave it on while also assigning a fixed address, the interface ends up with two addresses and two default routes, and the metric decides which one wins, not your intention.
Third, the file permissions. Since netplan 0.106, every invocation warns when the YAML file is readable by other users: Permissions for /etc/netplan/01-static.yaml are too open. Netplan configuration should NOT be accessible by others. That is not cosmetic, because files like these also hold Wi-Fi keys and tunnel credentials.
chmod 600 /etc/netplan/*.yaml
When the gateway sits outside your own subnet
With a single routed address on a /32, the kernel knows no direct path to the gateway and rejects the route. The journal then shows Could not set route: Network is unreachable, and a manual attempt with ip route add answers RTNETLINK answers: Network is unreachable. The fix is called on-link:
addresses:
- 203.0.113.10/32
routes:
- to: default
via: 192.0.2.1
on-link: true
With IPv6 the special case is the normal case: many networks hand out the link-local address fe80::1 as the gateway. Since netplan always attaches routes to an interface, via: "fe80::1" inside the block of the respective interface is enough.
Keeping cloud-init quiet
If there is a 50-cloud-init.yaml in that directory, add a lock as well, otherwise your work gets overwritten on the next boot:
echo 'network: {config: disabled}' > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
netplan try: the command that will not lock you out
The order is check, test, commit. netplan generate translates the YAML files without activating anything and reports syntax errors immediately. Only then comes the actual test.
netplan generate
netplan try --timeout 120
netplan apply
netplan try applies the new configuration and restores the old one automatically if you do not press Enter within the time limit. The default is 120 seconds. That is exactly why you never start with netplan apply on a remote server: a typo in the gateway address ends the SSH session, and without a fallback the story ends at the console or in a reinstall.
Do not rely on the rollback blindly. There are documented cases where
netplan tryfailed to restore connectivity after a timeout, especially when editing the file generated by cloud-init. After a rollback, always check whether the file on disk really is back in its previous state.
For everything netplan try does not cover (and for Debian with ifupdown, where the command does not exist at all), a second seatbelt has proven itself: a cleanup job that restores the backup if you do not check in on time.
nohup sh -c 'sleep 300; cp -a /root/netplan.bak/. /etc/netplan/; netplan apply' >/dev/null 2>&1 &
Make a note of the process number from the output. If everything works, end the job with kill. If you cannot get back in, it repairs itself within five minutes. Work inside tmux or screen on top of that, so a dropping SSH session does not take your editor with it in the middle of the change.
On KVM root servers the last resort is the console in the customer panel, which gets you onto the system even without a working network. On dedicated machines the way back is considerably more involved, so the precautions count double there.
Debian 13 and 12 with /etc/network/interfaces
After a standard installation, ifupdown manages the network. Its configuration is line oriented and uses separate blocks per address family:
auto lo
iface lo inet loopback
auto ens3
iface ens3 inet static
address 203.0.113.10/24
gateway 203.0.113.1
iface ens3 inet6 static
address 2001:db8:1234::2/64
gateway 2001:db8:1234::1
accept_ra 0
The auto ens3 applies to both blocks, you do not need a second auto line. If the gateway sits outside the subnet, the same idea as with netplan helps, just spelled out by hand:
iface ens3 inet static
address 203.0.113.10/32
post-up ip route add 192.0.2.1 dev ens3
post-up ip route add default via 192.0.2.1
pre-down ip route del default via 192.0.2.1
The biggest trap on Debian is not the syntax, it is the activation. systemctl restart networking takes the interface down briefly, and if the new configuration does not hold, the session is gone. ifdown ens3 && ifup ens3 is even more delicate, because the ifup never runs once the connection breaks during the ifdown. So set up the cleanup job described above first, and then run:
systemctl restart networking
systemctl status networking
Two messages show up here regularly. ifup: interface ens3 already configured means that ifupdown still records the interface as up, even though it may not be any more; the state lives in /run/network/ifstate. And Job for networking.service failed because the control process exited with error code is only the wrapper, the real reason is in journalctl -xeu networking, usually a default route assigned twice with RTNETLINK answers: File exists.
DNS under ifupdown
The obvious line dns-nameservers 9.9.9.9 in that file only takes effect when a helper is installed that copies it into /etc/resolv.conf, classically the package resolvconf. Without that helper the entry has no effect at all, and you only notice when names stop resolving: Temporary failure in name resolution. If you would rather not install anything, maintain /etc/resolv.conf directly and check with ls -l /etc/resolv.conf whether the file is a symlink, which would mean another service manages it.
Running Debian with systemd-networkd
If you already use systemd tooling on a Debian server, or you manage many interfaces and tunnels, systemd-networkd gives you a more consistent setup. The switch has three steps: write the configuration, enable the new service, retire the old one.
[Match]
Name=ens3
[Network]
Address=203.0.113.10/24
Address=2001:db8:1234::2/64
Gateway=203.0.113.1
Gateway=2001:db8:1234::1
DNS=9.9.9.9
DNS=2620:fe::fe
IPv6AcceptRA=no
This file belongs at /etc/systemd/network/10-ens3.network. For a gateway outside the subnet, append a route block of its own:
[Route]
Gateway=192.0.2.1
GatewayOnLink=yes
Then the switchover, ideally with the cleanup job covering your back again:
systemctl enable --now systemd-networkd
systemctl disable networking
networkctl status ens3
The output of networkctl status is the most honest feedback this topic has to offer. If it says State: routable (configured), the service accepted the file and applied it. If it says configuring or degraded, it tried and failed, no matter that the start command returned without an error.
The DNS side is a separate matter on Debian: systemd-networkd only feeds nameservers into name resolution when systemd-resolved is running and /etc/resolv.conf points at its file.
apt-cache policy systemd-resolved
This query is version dependent, and it misleads in a very quiet way. A standalone systemd-resolved package only exists from Debian 12 and Ubuntu 24.04 onwards, where the command prints a version. On Ubuntu 22.04 the service still sits inside the systemd package itself, so the command completes without an error but prints nothing at all. An installation attempt accordingly fails with Unable to locate package systemd-resolved. Empty output therefore does not mean the resolver is missing, it means the package does not exist on that release. Older systems such as Debian 11 behave the same way. On those releases, check this instead:
apt-cache policy systemd
systemctl status systemd-resolved
You enable the service with systemctl enable --now systemd-resolved, after which the usual symlink points at /run/systemd/resolve/stub-resolv.conf. If you do not want that, leave systemd-resolved out and write the nameservers statically into /etc/resolv.conf. What you should not do: half of each.
Adding extra IP addresses
Extra addresses are not a special case, they are simply one more entry. Under netplan the list grows:
addresses:
- 203.0.113.10/24
- 203.0.113.11/24
- 203.0.113.12/24
- "2001:db8:1234::2/64"
- "2001:db8:1234::3/64"
Under systemd-networkd you write several Address= lines one below the other. Under ifupdown you extend the existing definition instead of creating a second one:
iface ens3 inet static
address 203.0.113.10/24
gateway 203.0.113.1
post-up ip addr add 203.0.113.11/24 dev ens3
post-up ip addr add 203.0.113.12/24 dev ens3
pre-down ip addr del 203.0.113.11/24 dev ens3
pre-down ip addr del 203.0.113.12/24 dev ens3
The old notation with ens3:0 still works, but it is a relic from the days before the ip tool. It does not create real additional devices, only labels, and in firewall rules it causes more confusion than it is worth.
Three things typically go wrong with extra addresses. First, the address is not assigned on the provider side; no operating system configuration in the world makes an IP work that is not routed to your server, which is why a look at the customer panel is the first step and not the last. Second, a second address from the same network does not need a second default route; a second default gateway entry earns you RTNETLINK answers: File exists or, worse, alternating return paths. Third, with IPv6 the entire /64 is almost always routed to the server, so you can pick freely from that range, but only the address named by the provider is actually needed as a configured interface address.
To confirm that an extra address really carries traffic to the outside, test it explicitly from that source:
ping -c 3 -I 203.0.113.11 1.1.1.1
How to tell that it really holds
The fact that a command ran without an error message says little about the state of the network. These five checks do say something:
- The address is on the right interface:
ip -brief address showshows exactly the addresses you wanted and no leftovers from the old configuration. - The path out is correct, source address included:
ip route get 1.1.1.1names the expected gateway and the expectedsrc. - IPv6 has a default route of its own:
ip -6 route show defaultmust not be empty, otherwise everything quietly runs over IPv4. - Name resolution works independently of reachability:
getent hosts deb.debian.orgreturns an address, not just silence. - The only real test is a reboot. Only after that do you know whether the configuration comes from the file or still from RAM.
On Ubuntu, netplan status --all additionally prints a compact summary, and under systemd-networkd networkctl status does the same. When in doubt, both will show you that an address is in the file but was never applied.
Error messages, word for word
| Message | Cause and fix |
|---|---|
| Invalid YAML at /etc/netplan/01-static.yaml line 6 column 8: did not find expected key | A tab instead of spaces, or indentation that slipped. YAML does not allow tabs, use two spaces per level. |
| Error in network definition: unknown key 'gateway' | In netplan the key is not called gateway. Write an entry under routes with to: default. |
| `gateway4` has been deprecated, use default routes instead | Only a warning, the configuration still applies. Move to routes anyway. |
| Permissions for /etc/netplan/… are too open | Apply chmod 600 to the YAML file. |
| RTNETLINK answers: Network is unreachable | The gateway sits outside the configured subnet. Set on-link: true or GatewayOnLink=yes, or correct the prefix length. |
| RTNETLINK answers: File exists | The address or route already exists, usually because two configuration systems are working at the same time. |
| Error: Cannot find device "eth0" | The interface has a different name. Read it off with ip -brief link show. |
| Temporary failure in name resolution | Routing is fine, DNS is not. Check /etc/resolv.conf and find out which service writes that file. |
| ifup: interface ens3 already configured | ifupdown considers the interface up. Check the state in /run/network/ifstate. |
The four distributions side by side
| Debian 12 | Debian 13 | Ubuntu 22.04 | Ubuntu 24.04 | |
|---|---|---|---|---|
| Default tool | ifupdown | ifupdown | netplan | netplan |
| Main file | /etc/network/interfaces | /etc/network/interfaces | /etc/netplan/*.yaml | /etc/netplan/*.yaml |
| Alternative | systemd-networkd | systemd-networkd | systemd-networkd directly | systemd-networkd directly |
| Test without locking yourself out | your own cleanup job | your own cleanup job | netplan try | netplan try |
| systemd-resolved | separate package, inactive | separate package, inactive | part of systemd | separate package, active |
| netplan permission warning | not applicable | not applicable | from 0.106 | yes |
If you put the firewall into service right after configuring the network, make sure that rules for the new address and rules for IPv6 are handled separately. Our guide to UFW on Debian and Ubuntu shows how to set that up cleanly, and hardening an SSH server covers how to secure access afterwards.
Short version for the next server
Read the values instead of guessing, make a backup, write the configuration, test it with netplan try or a cleanup job of your own, commit it, reboot, and only then tick it off. Stick to that order and the worst case costs you five minutes. Cut it short and the worst case costs you the server, until someone sits down at the console.
Frequently asked questions
Why is my server unreachable after netplan apply?
Does Debian 13 use netplan?
What replaces gateway4 in netplan?
Why does my /etc/resolv.conf keep getting overwritten?
How do I add an extra IP address?
My gateway is outside the subnet, what now?
2026 KernelHost GmbH. All rights reserved. This guide is protected by copyright. Republishing it on other websites, in whole, in part or in edited form, is not permitted without our written consent. Quoting with a source credit and a link is expressly welcome.

