DIY Debian Router - Part 2: Network configuration with systemd-networkd

Oct 7, 2025

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 resolver
  • nftables: Modern Linux firewall framework
  • dnsutils: DNS troubleshooting tools (dig, nslookup)
  • iperf3: Network performance testing
  • wireguard: 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.

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 sshd

Enable serial console

If your system has a serial console, I would recommended enabling it as a last resort access method:

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-networkd

systemd-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 enp8s0 with your actual WAN interface name. Use ip link to 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 ISP
  • IPv6AcceptRA=yes: Accept IPv6 Router Advertisements from ISP for SLAAC
  • UseDNS=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, enp7s0 with 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 ports
  • Bridge=br0: Attach interfaces to bridge
  • ConfigureWithoutCarrier=yes: Allow configuration even if no cable is connected
  • RequiredForOnline=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 gateway
  • Address=fd09:dead:beef::1/48: Static IPv6 ULA address for LAN gateway
  • IPv6AcceptRA=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 carrier
  • RequiredForOnline=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.

RSS
https://yusefkarim.absurdum.ca/posts/feed.xml