systemd-networkd: Minimal Linux Server Network Configuration — Replace NetworkManager with .network Files

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

2 AM. The VPS just rebooted after a kernel update, and the network won’t come up. SSH is gone. Console access through the VNC provider is painfully laggy. NetworkManager is throwing a device unmanaged error. Exactly the kind of failure you never want to see on production.

After that incident, I removed NetworkManager from every headless server and switched to systemd-networkd. No GUI needed, no bloated daemon, just a few text files and you’re done.

Quick Start — Done in 5 Minutes

If your server currently has network access and you want to switch to systemd-networkd right now, follow these steps in exactly this order. Get the order wrong and you’ll lose SSH just like that.

First, identify the name of the interface you’re currently using:

ip link show
# Example output:
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
# 3: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> ...

Create a configuration file for that interface (replace eth0 with your actual interface name):

sudo nano /etc/systemd/network/20-eth0.network

DHCP configuration (most common for VPS):

[Match]
Name=eth0

[Network]
DHCP=yes
DNS=1.1.1.1
DNS=8.8.8.8

Or static IP configuration:

[Match]
Name=eth0

[Network]
Address=203.0.113.10/24
Gateway=203.0.113.1
DNS=1.1.1.1
DNS=8.8.8.8

Enable systemd-networkd and disable NetworkManager — in this exact order:

# Enable systemd-networkd first
sudo systemctl enable --now systemd-networkd

# Enable systemd-resolved (the bundled DNS resolver)
sudo systemctl enable --now systemd-resolved

# Link the resolver config
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

# Only then disable NetworkManager
sudo systemctl disable --now NetworkManager

Verify immediately:

networkctl status
ping -c 3 1.1.1.1
ping -c 3 google.com

Both pings succeed — you’re done. Under 5 minutes.

In-Depth Explanation — Understand It So You’re Not Afraid

What is systemd-networkd and why use it on a server?

systemd-networkd is a network management daemon built directly into systemd — which virtually every modern Linux distro is already running. Nothing extra to install. The same systemd ecosystem also powers socket activation for keeping idle service memory usage near zero.

Compared to NetworkManager, the practical difference comes down to this:

  • NetworkManager: Designed for desktops — auto-scanning WiFi, GUI-based VPN, mobile hotspot. None of that is needed on a headless server, but you still pay the overhead for all of it.
  • systemd-networkd: Text files plus a reload and you’re done. No GUI, no helper daemons, faster boot times, and easy to manage with Ansible or scripts.

On a headless server with no display and no WiFi, NetworkManager is pure overhead. I’ve measured it firsthand: boot time dropped by 3–4 seconds after switching to systemd-networkd on Debian/Ubuntu VPS instances. Replace GRUB2 with systemd-boot on top of that and the total improvement gets even more dramatic.

Structure of a .network file

Configuration files live in /etc/systemd/network/, named using the format XX-name.network — the leading number sets the priority order (lower number = higher priority). Since all of these live under /etc, keeping them under version control with etckeeper is a simple safety net worth adding.

A .network file has 3 main sections:

[Match]
# Determines which interface this config applies to
Name=eth0          # Match by interface name
# MACAddress=aa:bb:cc:dd:ee:ff  # Or match by MAC address

[Network]
# Main network configuration
DHCP=yes           # Automatically obtain IP from DHCP
# Address=x.x.x.x/24   # Or set a static IP
# Gateway=x.x.x.x
DNS=1.1.1.1

[DHCP]
# DHCP client settings
RouteMetric=10     # Route metric (useful when multiple interfaces are present)
UseDNS=yes

The [Match] section is the most critical — it determines which interface this file applies to. If nothing matches, the file is silently ignored. This is where people most often go wrong by misspelling the interface name. Using udev rules to assign fixed, predictable names to your interfaces eliminates this class of error entirely.

systemd-resolved — the bundled DNS resolver

systemd-networkd typically works alongside systemd-resolved to handle DNS. When you declare DNS=1.1.1.1 in your .network file, systemd-resolved picks it up and manages domain name resolution.

The /etc/resolv.conf file needs to point to systemd-resolved’s stub resolver:

# Check current resolv.conf
cat /etc/resolv.conf

# If not yet linked, create the symlink
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

# Check which DNS servers are currently in use
resolvectl status

Advanced — Real-World Scenarios

Server with 2 interfaces (management + data)

A common setup: a server with eth0 for management (SSH) and eth1 for application traffic.

# Config for eth0 — management interface
# /etc/systemd/network/10-management.network
[Match]
Name=eth0

[Network]
Address=10.0.0.5/24
Gateway=10.0.0.1
DNS=10.0.0.1

[Route]
Gateway=10.0.0.1
Metric=100
# Config for eth1 — data network
# /etc/systemd/network/20-data.network
[Match]
Name=eth1

[Network]
DHCP=yes

[DHCP]
RouteMetric=200
UseRoutes=yes

Lower metric = higher route priority. eth0 (metric 100) will be the primary interface.

Wildcard matching — apply to multiple interfaces at once

[Match]
Name=eth*

[Network]
DHCP=yes

This is useful when you have eth0, eth1, eth2 and want all of them to use DHCP. Use it carefully though — wildcards can sometimes match interfaces you didn’t intend to include.

VLAN configuration

# Create a netdev file for the VLAN interface
# /etc/systemd/network/30-vlan100.netdev
[NetDev]
Name=eth0.100
Kind=vlan

[VLAN]
Id=100
# Network file for the VLAN
# /etc/systemd/network/30-vlan100.network
[Match]
Name=eth0.100

[Network]
Address=192.168.100.5/24

systemd-networkd supports VLANs, bridges, and bonding natively — no additional packages required.

Practical Tips — Lessons Learned from Production

Three years, over 10 VPS instances, a few SSH lockouts at exactly the wrong time in the middle of the night — I learned the following the hard way. Network configuration on production cannot afford to be wrong, especially when your provider doesn’t offer console access like Vultr or Hetzner.

Tip 1: Always test on a staging server first

I make it a habit to clone the configuration onto a cheap test VPS first, then reboot it several times to confirm the network comes back up correctly after each boot.

# Reload config without rebooting
sudo networkctl reload

# Check status of each interface
networkctl list
networkctl status eth0

Tip 2: Debugging when the network won’t come up

# View networkd logs
journalctl -u systemd-networkd -n 50

# Follow logs in real time
journalctl -u systemd-networkd -f

# Check whether the .network file is being parsed correctly
networkctl cat eth0

Tip 3: Back up NetworkManager config before removing it

# Back up NetworkManager configuration
sudo cp -r /etc/NetworkManager /etc/NetworkManager.bak

# View what config the current connection is using
nmcli connection show

Tip 4: Naming convention for .network files

I use this convention: lower number = higher priority = more critical interface.

  • 10-loopback.network — loopback (if configuration is needed)
  • 20-management.network — SSH/management interface
  • 30-data.network — data interface
  • 90-default.network — wildcard fallback for interfaces without a dedicated file

Tip 5: You don’t need to remove NetworkManager if you’re unsure

You really only need to disable NetworkManager and enable systemd-networkd. Removing it entirely can cause issues if other packages depend on it. Disabling is much safer:

sudo systemctl disable NetworkManager
sudo systemctl stop NetworkManager
# No need for apt remove

Tip 6: When using cloud-init on a VPS

Many VPS providers use cloud-init to configure networking at provisioning time. The problem is that cloud-init can override your .network files after every reboot. Check for it:

cat /etc/cloud/cloud.cfg | grep network

# To disable cloud-init from managing the network
sudo mkdir -p /etc/cloud/cloud.cfg.d/
echo 'network: {config: disabled}' | sudo tee /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

I discovered this after finishing the setup, rebooting, then spending 30 minutes debugging at 2 AM before realizing cloud-init had overwritten everything. More frustrating than the original problem.

Of course, systemd-networkd isn’t for everyone. Need complex WiFi? Multiple VPN profiles? A desktop environment? NetworkManager handles those better. But a headless server running 24/7 is a different story — static config files you can deploy and automate with a simple Bash script, fewer daemons, fewer things that can blow up at exactly the wrong moment. What I need at 2 AM isn’t a smart tool. It’s a tool that stays out of my way.

Share: