DIY Debian Router - Part 2: Network configuration with systemd-networkd
Introduction
This is Part 2 of the DIY Debian Router series. See Part 1 for the series introduction and links to all parts.
This part covers the implementation of WAN/LAN separation, Linux bridge setup for LAN ports, and IPv4/IPv6 addressing using systemd-networkd, the network management daemon included with systemd. As discussed in Part 1, I'll be using five Ethernet interfaces: one WAN uplink and four LAN ports aggregated into a Layer 2 bridge.
Initial system preparation
Package installation
Install all packages (used throughout all posts in this series):
apt install ca-certificates curl wget openssh-server neovim \
dnsutils ethtool iperf3 nftables conntrack man zip unzip gnupg2 rsync tcpdump \
debian-archive-keyring kea unbound wireguard qrencode ddclient
Package summary:
kea: ISC Kea DHCP server (DHCPv4/DHCPv6)unbound: Validating recursive DNS resolvernftables: Modern Linux firewall frameworkdnsutils: DNS troubleshooting tools (dig,nslookup)iperf3: Network performance testingwireguard: WireGuard userland utils
SSH server access and hardening
To enable SSH, run:
systemctl enable --now sshd
Before proceeding with network configuration, secure SSH access to prevent lockout or unauthorized access. Start by ensuring you have SSH keys properly configured before applying these settings to avoid lockout (I hope this goes without saying, but locking yourself out of a headless router is genuinely not fun).
Edit /etc/ssh/sshd_config and enforce the following settings:
PermitRootLogin no
PasswordAuthentication no
This disables root login and password-based authentication, requiring SSH key authentication for all access.
Restart SSH to apply changes:
systemctl restart sshdEnable serial console
If your system has a serial console, I would highly recommend enabling it as a last resort access method (more on this in a moment - trust me, you'll thank yourself later):
Edit your /etc/default/grub to contain:
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"
Update grub:
update-grub
Enable serial port services (this might not be entirely needed, but hey):
systemctl enable --now serial-getty@ttyS0.service
Reboot your system and test with a serial cable to confirm everything works as expected.
Disable conflicting network managers
Debian may have multiple network management systems active. Disable legacy networking and NetworkManager if present:
# Disable traditional networking
systemctl disable --now networking.service
# Disable ifupdown if installed
systemctl disable --now ifup@.service
# Disable NetworkManager if installed
systemctl disable --now NetworkManager
# Disable dhcpcd if installed
systemctl disable --now dhcpcd
Enable systemd-networkd:
systemctl enable --now systemd-networkdsystemd-networkd configuration
Network configuration in systemd-networkd uses .network, .netdev, and .link files in /etc/systemd/network/. Files are processed in lexicographical order.
Network device definitions
First, define the bridge device for LAN aggregation.
/etc/systemd/network/10-br0.netdev
[NetDev]
Name=br0
Kind=bridge
[Bridge]
STP=no
This creates a bridge named br0 with Spanning Tree Protocol disabled (not needed for a simple single-bridge topology).
WAN interface configuration
Configure the WAN interface for IPv4 DHCP and IPv6 autoconfiguration.
✏️ Replace
enp8s0with your actual WAN interface name. Useip linkto identify interface names.
/etc/systemd/network/20-wan.network
[Match]
Name=enp8s0
[Network]
DHCP=yes
IPv6AcceptRA=yes
[DHCPv4]
UseDNS=no
UseRoutes=yes
RouteMetric=100
[IPv6AcceptRA]
UseDNS=no
RouteMetric=100
[Link]
RequiredForOnline=yes
Configuration breakdown:
DHCP=yes: Enable DHCPv4 client for obtaining public IPv4 address from ISPIPv6AcceptRA=yes: Accept IPv6 Router Advertisements from ISP for SLAACUseDNS=no: Do not use DNS servers from DHCP/RA (router runs its own DNS resolver in Part 3)UseRoutes=yes: Accept default gateway from DHCP (DHCPv4 section only)- IPv6AcceptRA section automatically accepts routes from RAs by default
RouteMetric=100: Route preference (lower is preferred)RequiredForOnline=yes: System considers network "online" only when WAN is configured
LAN bridge member interfaces
Add the four LAN ports to the bridge.
✏️ Replace
enp4s0,enp5s0,enp6s0,enp7s0with your actual LAN interface names.
/etc/systemd/network/30-lan-members.network
[Match]
Name=enp4s0 enp5s0 enp6s0 enp7s0
[Network]
Bridge=br0
ConfigureWithoutCarrier=yes
[Link]
RequiredForOnline=no
Configuration breakdown:
Name=enp4s0 enp5s0 enp6s0 enp7s0: Apply configuration to all four LAN portsBridge=br0: Attach interfaces to bridgeConfigureWithoutCarrier=yes: Allow configuration even if no cable is connectedRequiredForOnline=no: Individual LAN ports don't need to be up for system to be "online"
LAN bridge interface configuration
Configure the bridge interface with static IPv4 and IPv6 addressing.
✏️ Recall from Part 1, the static LAN IPv6 address below (
fd09:dead:beef::/48) is a fake example. You should use a proper randomly generated address instead, per RFC4193.
/etc/systemd/network/40-br0.network
[Match]
Name=br0
[Network]
Address=192.168.0.1/24
Address=fd09:dead:beef::1/48
IPv6AcceptRA=no
ConfigureWithoutCarrier=yes
[Link]
RequiredForOnline=carrier
Configuration breakdown:
Address=192.168.0.1/24: Static IPv4 address for LAN gatewayAddress=fd09:dead:beef::1/48: Static IPv6 ULA address for LAN gatewayIPv6AcceptRA=no: Do not accept RAs on LAN interface (router generates RAs, doesn't consume them)ConfigureWithoutCarrier=yes: Configure bridge even if no member ports have carrierRequiredForOnline=carrier: Bridge needs at least one port with carrier to be considered online
IPv6 Global Unicast Addresses (GUA) will be configured dynamically via DHCPv6-PD in Part 5.
Interface verification
Restart systemd-networkd to apply configuration:
systemctl restart systemd-networkd
Verify interface status:
networkctl status
Check detailed interface information:
networkctl status enp8s0
networkctl status br0
Verify IP addresses:
ip addr show enp8s0
ip addr show br0
Check bridge status:
bridge link show
Verify routing tables:
ip route show
ip -6 route show
IPv4 should have a default route via the WAN interface's DHCP gateway. IPv6 should have default routes via the WAN interface's RA-learned gateway (if available) and local routes for the ULA prefix.
Troubleshooting common issues
If bridge member ports do not attach to the bridge, verify the configuration file is being loaded:
systemctl status systemd-networkd
journalctl -u systemd-networkd -n 50
Check for syntax errors in .network files then reload:
networkctl reload
networkctl status
Manually bring up an interface:
networkctl reconfigure br0
Check dmesg for physical link detection issues (cable, NIC driver).
If WAN interface not receiving DHCP, verify DHCP client is running:
networkctl status enp8s0
journalctl -u systemd-networkd | grep -i dhcp
If DHCP fails, check ISP-side issues (modem reboot, provisioning, etc).
Manually trigger DHCP renewal:
networkctl renew enp8s0
If IPv6 is not configured on WAN, ensure IPv6 is enabled globally (covered in Part 7):
sysctl net.ipv6.conf.all.disable_ipv6
Should return 0. If 1, enable IPv6:
Verify RA reception:
# Install rdisc6 if not already installed
apt install ndisc6
rdisc6 enp8s0
This sends Router Solicitations and displays received RAs. If no response, the ISP may not provide IPv6, or the modem/ONT is not forwarding RAs.
Per-interface security hardening
Per-interface security hardening is applied via sysctl parameters in Part 7. These settings include:
Next steps
With network interfaces configured and secured, the router has functional WAN/LAN connectivity but no service layer (DNS, DHCP, firewall). Part 3 covers DNS resolver deployment with Unbound, providing recursive DNS resolution and blocklist integration for the LAN.