Turning a single-board computer into a network gateway is a rite of passage, but most online guides are hopelessly outdated. They reference dhcpcd and iptables—both of which have been deprecated or replaced in Raspberry Pi OS Bookworm. If you are building a Raspberry Pi router in 2026, you need to use NetworkManager and nftables, and you need hardware that won't bottleneck at 1Gbps.
This guide gives you the exact hardware stack, the modern Bookworm configuration steps, and a production-ready Python failover script to keep your network online when the primary WAN drops.
The Verdict: Which Raspberry Pi Router Build Should You Choose?
Before ordering parts, match your use case to the right hardware topology. The biggest mistake makers make is pairing a high-speed USB network adapter with a board that lacks the PCIe or USB 3.0 bandwidth to support it.
| Scenario | Hardware Stack | Max Throughput | Verdict |
|---|---|---|---|
| Max Throughput DIY | Pi 5 (8GB) + RTL8156B USB-C 2.5GbE | ~2.3 Gbps (NAT) | DEFAULT PICK. Best balance of cost, speed, and simplicity. |
| Industrial / Headless | Compute Module 5 + Dual-GbE Carrier | ~940 Mbps (Dual GbE) | Choose when you need PoE, RS485, or locked-down physical ports. |
| Ultra-Budget / IoT | Pi 4 (4GB) + USB 3.0 Gigabit Adapter | ~600 Mbps (CPU limited) | Choose only if routing <100Mbps traffic for isolated IoT VLANs. |
Parts List & Hardware Interface Mapping
This build targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (64-bit, Bookworm). The 8GB model is chosen over the 4GB to provide ample RAM for nftables state tables and Python monitoring daemons without swapping.
Bill of Materials (BOM)
- Board: Raspberry Pi 5 (8GB) - ~$80
- Cooling: Raspberry Pi Active Cooler - ~$5 (Mandatory for sustained NAT routing)
- Power: Raspberry Pi 27W USB-C PD Power Supply - ~$12 (Crucial for USB bus stability)
- LAN Adapter: TP-Link UE300C or UGREEN USB-C to 2.5G Ethernet (RTL8156B) - ~$30
- Storage: Samsung EVO Select 64GB microSD - ~$10
- Status LEDs: 2x 3mm LEDs (Green/Red) with 220Ω resistors
Hardware Interface & GPIO Status Mapping
Since we are building a router, "pin mapping" refers to network interfaces and physical GPIO status indicators.
| Interface / Pin | Role | Configuration Notes |
|---|---|---|
eth0 (Onboard GbE) |
WAN (Internet) | Connected to ISP modem. DHCP client. |
eth1 (USB 2.5GbE) |
LAN (Local) | Connected to access point/switch. Static IP, DHCP server. |
| GPIO 17 (Pin 11) | Green LED | Indicates WAN link is active and passing traffic. |
| GPIO 27 (Pin 13) | Red LED | Indicates WAN failure / Failover mode active. |
Step-by-Step: NetworkManager and nftables Configuration
Bookworm uses NetworkManager by default. Do not install dhcpcd; it will conflict and break your routing table.
- Flash and Boot: Flash Raspberry Pi OS Lite (64-bit, Bookworm) using Raspberry Pi Imager. Enable SSH and set your hostname to
pi-routerin the advanced settings. - Configure WAN (eth0): Ensure
eth0is set to automatic DHCP.sudo nmcli connection add type ethernet con-name WAN ifname eth0 ipv4.method auto ipv6.method ignore - Configure LAN (eth1): Set a static IP for the internal network.
sudo nmcli connection add type ethernet con-name LAN ifname eth1 ipv4.method manual ipv4.addresses 192.168.50.1/24 ipv6.method ignore - Enable IP Forwarding: Edit
/etc/sysctl.confand uncomment or add:
Apply withnet.ipv4.ip_forward=1sudo sysctl -p. - Configure NAT with nftables: Install the daemon and flush legacy rules.
sudo apt update && sudo apt install nftables -y sudo systemctl enable nftables sudo systemctl start nftables - Write the nftables Ruleset: Create
/etc/nftables.confwith the following masquerade logic:
Reload with#!/usr/sbin/nft -f flush ruleset table inet nat { chain postrouting { type nat hook postrouting priority 100; policy accept; oifname "eth0" masquerade } }sudo nft -f /etc/nftables.conf. - Install DNS/DHCP Server: Use
dnsmasqto hand out IPs on the LAN.
Editsudo apt install dnsmasq -y/etc/dnsmasq.confto bind only to the LAN interface:
Restart the service:interface=eth1 dhcp-range=192.168.50.10,192.168.50.200,12h dhcp-option=6,192.168.50.1,1.1.1.1sudo systemctl restart dnsmasq.
Python Failover & Monitoring Script
Consumer ISP connections drop. This Python daemon monitors the WAN interface by pinging a reliable external IP. If the WAN drops, it lights up the Red GPIO LED and logs the outage. It uses gpiozero (native to Bookworm) and subprocess with strict error handling.
Target Board: Raspberry Pi 5 | OS: Bookworm 64-bit | Python 3.11+
#!/usr/bin/env python3
import subprocess
import time
import logging
from gpiozero import LED
# Hardware Interface Definitions
WAN_INTERFACE = "eth0"
PING_TARGET = "8.8.8.8"
CHECK_INTERVAL = 10 # seconds
# GPIO Pin Definitions (BCM numbering)
LED_WAN_OK = LED(17) # Green
LED_FAILOVER = LED(27) # Red
# Logging setup
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[logging.FileHandler("/var/log/router_monitor.log"), logging.StreamHandler()]
)
logger = logging.getLogger("PiRouterMonitor")
def check_wan_link() -> bool:
"""Pings target via WAN interface. Returns True if successful."""
try:
# -I forces ping out of specific interface, -c count, -W timeout
cmd = ["ping", "-I", WAN_INTERFACE, "-c", "1", "-W", "2", PING_TARGET]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return True
except subprocess.CalledProcessError as e:
logger.warning(f"Ping failed on {WAN_INTERFACE}: {e.stderr.strip()}")
return False
except Exception as e:
logger.error(f"Unexpected subprocess error: {e}")
return False
def main():
logger.info("Starting Raspberry Pi Router Monitor...")
wan_is_up = True
# Initial state
LED_WAN_OK.on()
LED_FAILOVER.off()
try:
while True:
link_status = check_wan_link()
if link_status and not wan_is_up:
logger.info("WAN link RESTORED.")
wan_is_up = True
LED_WAN_OK.on()
LED_FAILOVER.off()
elif not link_status and wan_is_up:
logger.error("WAN link LOST! Entering failover state.")
wan_is_up = False
LED_WAN_OK.off()
LED_FAILOVER.on()
# Optional: Trigger nmcli to switch to backup WAN here
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
logger.info("Monitor stopped by user.")
finally:
LED_WAN_OK.close()
LED_FAILOVER.close()
if __name__ == "__main__":
main()
/var/log/, either run it as root (via a systemd service) or change the log path to a user-writable directory like /home/pi/router_monitor.log.
Debugging: Exact Error Strings and Ranked Causes
When your Raspberry Pi router fails to pass traffic, do not blindly reboot. Read the logs. Here are the three most common fatal errors in Bookworm, ranked by probability.
1. "RTNETLINK answers: File exists"
- Cause A (Most Likely): You have duplicate default routes. NetworkManager and a leftover
dhcpcdconfig are both trying to assign a gateway toeth0. - Cause B: You manually added an
ip route add defaultcommand in/etc/rc.localwhile NetworkManager was already managing the interface. - Fix: Run
ip route show. If you see two default routes, purge the legacy DHCP client:sudo apt purge dhcpcd5and reboot.
2. "undervoltage detected!" (in dmesg)
- Cause A: You are using a third-party USB-C phone charger instead of the official 27W PD supply. The Pi 5 requires 5V/5A to maintain full USB 3.0 bus power.
- Cause B: The RTL8156B adapter is drawing too much current during heavy NAT translation, causing a brownout that resets the USB bus.
- Fix: Verify with
vcgencmd get_throttled. If it returns anything other than0x0, replace the power supply immediately. The USB network adapter will randomly drop offline until this is fixed.
3. "nftables.service: Failed with result 'exit-code'"
- Cause A: Syntax error in
/etc/nftables.conf. Usually a missing semicolon or incorrect interface name (e.g., usingenx00e04c...instead ofeth0). - Cause B: The kernel module
nft_masqis not loaded. - Fix: Test the config manually with
sudo nft -c -f /etc/nftables.conf(the-cflag checks syntax without applying). Fix any reported line numbers.
The First Three Things to Check When Routing Fails
- Interface Names: Run
ip link show. USB adapters sometimes enumerate asenx...instead ofeth1depending on yourudevrules. Update yournmcliandnftablesconfigs to match the actual string. - Power State: Run
vcgencmd get_throttled. USB bus drops are the #1 cause of "sudden" router reboots. - Firewall State: Run
sudo nft list ruleset. Ensure your masquerade rule is actually loaded in memory and pointing to the correct outbound interface.
Extending the Build: Wi-Fi AP and Traffic Shaping
Once your wired Raspberry Pi router is stable, you will likely want to expand its capabilities. Here is how to extend or simplify the stack based on your goals.
How to Simplify: Flash OpenWrt
If managing nmcli, nftables, and dnsmasq manually feels like overkill, abandon Raspberry Pi OS entirely. The OpenWrt project maintains dedicated images for the Pi 5. OpenWrt provides a unified web UI (LuCI) that handles VLANs, firewall zones, and DHCP in a single pane of glass. It is the ultimate "simplify" move for a dedicated router appliance.
How to Extend: Add a Wi-Fi 6 Access Point
The Pi 5's onboard Wi-Fi is adequate for clients, but terrible for routing as an Access Point (AP) due to antenna limitations and regulatory domain locks. To extend this build into a wireless router:
- Purchase a USB Wi-Fi 6 adapter with an MT7921AUN or RTL8832CU chipset that explicitly supports AP mode in Linux.
- Install
hostapdand bind it to the newwlan0interface. - Bridge
wlan0toeth1using NetworkManager so wireless clients receive IPs from the same192.168.50.xpool as your wired LAN.
How to Extend: Traffic Shaping with Cake
If your ISP connection is high-latency or prone to bufferbloat, enable the CAKE queue discipline. It is built into the Bookworm kernel. Add this to your nftables or tc (traffic control) setup to prioritize gaming and VoIP traffic over bulk downloads, drastically reducing ping spikes when the network is saturated.
Building a router on a Pi 5 is no longer a compromised science experiment. With the RTL8156B adapter, Bookworm's NetworkManager, and proper thermal management, you have a gigabit-class gateway that rivals commercial off-the-shelf hardware, with the added benefit of running custom Python daemons directly on the silicon.






