If you want to make a router with Raspberry Pi, the built-in single Ethernet port and USB bus limitations won't cut it for a reliable home gateway. The definitive 2026 approach uses the Raspberry Pi 5's exposed PCIe lane paired with a dual-NIC HAT, running bare-metal Raspberry Pi OS Lite with nftables for NAT and dnsmasq for DHCP/DNS. This bypasses the USB 3.0 interrupt storms that plague older dongle-based builds and delivers true gigabit-plus throughput.
Project Spec Sheet & Difficulty Rating
Target Board Variant: Raspberry Pi 5 (8GB RAM) running Raspberry Pi OS Lite (64-bit, Bookworm/Trixie)
Difficulty: Intermediate (Requires Linux CLI and basic networking knowledge)
Time to Build: 2 hours (Hardware assembly: 30m, Software config: 90m)
Estimated Cost: ~$155 USD
Hardware BOM & Interface Mapping
Do not use USB-to-Ethernet adapters for a primary router. The Pi's USB controller shares bandwidth and interrupt lines, leading to packet drops under heavy concurrent connections. Using the PCIe Gen 2.0 x1 interface on the Pi 5 gives the network controller a direct, dedicated lane to the BCM2712 SoC.
| Component | Exact Model / Variant | Approx. Price |
|---|---|---|
| Compute Board | Raspberry Pi 5 (8GB) | $80 |
| Network HAT | Waveshare Dual 2.5G Gigabit Ethernet PCIe HAT (RTL8125BG chipset) | $45 |
| Power Supply | Official Raspberry Pi 27W USB-C PD Power Supply | $12 |
| Thermal | Official Raspberry Pi Active Cooler | $5 |
| Storage | 32GB SanDisk Extreme A2 U3 microSD | $10 |
| Interconnect | M.2 PCIe FPC Cable (usually included with HAT) | $3 |
GPIO & Hardware Interface Pin Mapping
While the network traffic flows over PCIe, we use the 40-pin header for out-of-band management, status indicators, and safe shutdown signaling.
| Function | Pi 5 Pin / Interface | Notes |
|---|---|---|
| PCIe Data Lane | J4 PCIe Connector (Gen 2.0 x1) | Requires dtparam=pciex1 in config.txt |
| UART Console (TX/RX) | GPIO 14 (TXD) / GPIO 15 (RXD) | Headless debugging if network fails |
| PWM Fan Control | GPIO 18 | Active Cooler tachometer/PWM input |
| WAN Status LED | GPIO 24 | Wired to external panel LED via 330Ω resistor |
| LAN Status LED | GPIO 25 | Wired to external panel LED via 330Ω resistor |
| I2C UPS Comms | GPIO 2 (SDA) / GPIO 3 (SCL) | For future UPS HAT graceful shutdown |
Step-by-Step: Configuring the Pi 5 Router
Before running the automation script, you must prepare the base OS. Flash Raspberry Pi OS Lite (64-bit) using the Raspberry Pi Imager. In the Imager's advanced settings, enable SSH and set your locale, but do not configure WiFi or a static IP yet.
- Enable PCIe: Boot the Pi, open
/boot/firmware/config.txt, and adddtparam=pciex1anddtparam=pciex1_gen=2to the bottom. Reboot. - Kill the DNS Squatter: Raspberry Pi OS ships with
systemd-resolved, which hogs port 53. We must disable it sodnsmasqcan act as our local DNS forwarder.sudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved sudo rm /etc/resolv.conf sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf - Install Dependencies: Install the firewall, DHCP server, and network manager.
sudo apt update sudo apt install nftables dnsmasq systemd-networkd -y - Enable IP Forwarding: Edit
/etc/sysctl.confand uncomment or addnet.ipv4.ip_forward=1. Apply withsudo sysctl -p.
eth0). Always keep a USB-to-TTL serial cable connected to GPIO 14/15, or connect your PC directly to the new LAN port (eth2) to recover access.
The Configuration Script
This bash script configures the interfaces, sets up the NAT rules, initializes the GPIO status LEDs, and starts the DHCP server. It includes strict error handling to prevent bricking your network access if interface names change.
#!/bin/bash
# Pi 5 Dual-NIC Router Configuration Script
# Target: Raspberry Pi OS Lite (64-bit)
set -euo pipefail
# --- Interface & GPIO Pin Definitions ---
WAN_IF="eth1" # PCIe NIC Port 1 (Connects to ISP Modem)
LAN_IF="eth2" # PCIe NIC Port 2 (Connects to Home Switch)
MGMT_IF="eth0" # Built-in Pi Ethernet (Keep for local management)
GPIO_WAN_LED=24
GPIO_LAN_LED=25
LAN_SUBNET="192.168.50.1/24"
# --- Error Handling: Verify Interfaces Exist ---
check_interface() {
if ! ip link show "$1" &> /dev/null; then
echo "FATAL: Interface $1 not found. Udev may have renamed it."
echo "Available interfaces:"
ip -o link show | awk -F': ' '{print $2}'
exit 1
fi
}
check_interface "$WAN_IF"
check_interface "$LAN_IF"
# --- GPIO LED Initialization ---
echo "Configuring GPIO LEDs..."
for pin in $GPIO_WAN_LED $GPIO_LAN_LED; do
if [ ! -d "/sys/class/gpio/gpio$pin" ]; then
echo "$pin" > /sys/class/gpio/export
fi
echo "out" > "/sys/class/gpio/gpio$pin/direction"
echo "0" > "/sys/class/gpio/gpio$pin/value"
done
# --- Network Configuration (systemd-networkd) ---
echo "Writing network profiles..."
cat < /etc/systemd/network/10-wan.network
[Match]
Name=$WAN_IF
[Network]
DHCP=yes
EOF
cat < /etc/systemd/network/20-lan.network
[Match]
Name=$LAN_IF
[Network]
Address=$LAN_SUBNET
DHCPServer=yes
EOF
systemctl enable systemd-networkd
systemctl restart systemd-networkd
# --- nftables NAT & Firewall Rules ---
echo "Applying nftables firewall..."
cat < /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iifname { "lo", "$LAN_IF", "$MGMT_IF" } accept
iifname "$WAN_IF" ct state established,related accept
iifname "$WAN_IF" tcp dport 22 drop # Block SSH from WAN
}
chain forward {
type filter hook forward priority 0; policy drop;
iifname "$LAN_IF" oifname "$WAN_IF" accept
iifname "$WAN_IF" oifname "$LAN_IF" ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "$WAN_IF" masquerade
}
}
EOF
systemctl enable nftables
systemctl restart nftables
# --- dnsmasq DHCP/DNS Setup ---
echo "Configuring dnsmasq..."
cat < /etc/dnsmasq.d/router.conf
interface=$LAN_IF
bind-interfaces
dhcp-range=192.168.50.10,192.168.50.200,12h
dhcp-option=6,192.168.50.1,1.1.1.1
server=1.1.1.1
server=1.0.0.1
EOF
systemctl enable dnsmasq
systemctl restart dnsmasq
# Light up LEDs to indicate success
echo "1" > "/sys/class/gpio/gpio$GPIO_WAN_LED/value"
echo "1" > "/sys/class/gpio/gpio$GPIO_LAN_LED/value"
echo "Router configuration complete. Rebooting in 5 seconds..."
sleep 5
reboot
Debugging: Boot Failures & Port Conflicts
When building a Linux router from scratch, you will inevitably hit interface naming quirks or daemon conflicts. Here is how to troubleshoot the most common points of failure.
The "Address already in use" Error
If dnsmasq fails to start and systemctl status dnsmasq outputs this exact string:
dnsmasq: failed to create listening socket for port 53: Address already in use
Ranked Causes & Fixes:
- Cause:
systemd-resolvedrestarted itself after a reboot or update.
Fix: Runsudo systemctl mask systemd-resolved(masking prevents it from being pulled back in by dependencies, unlikedisable). - Cause: A stale PID file from a crashed DNS service.
Fix: Runsudo rm /var/run/dnsmasq.pidand restart the service. - Cause: Another package (like
bind9orunbound) was installed as a dependency.
Fix: Find the squatter withsudo ss -tulpn | grep 53andsudo apt purgethe offending package.
The First 3 Things to Check When Routing Fails
If your LAN devices get IP addresses but cannot reach the internet, run through this exact diagnostic path:
- Check IP Forwarding State: Run
cat /proc/sys/net/ipv4/ip_forward. If it returns0, yoursysctlconfig didn't persist. Fix it withsudo sysctl -w net.ipv4.ip_forward=1. - Verify Interface Naming (The udev trap): Run
ip link show. If your PCIe HAT interfaces are namedenp1s0andenp1s1instead ofeth1andeth2, yournftablesrules are pointing to ghost interfaces. Update the variables in the script or write a custom/etc/udev/rules.d/70-persistent-net.rulesfile to force the names. - Confirm WAN DHCP Lease: Run
ip addr show eth1. If there is no public or modem-range IP address, your ISP might be enforcing MAC address binding. Clone your old router's MAC address usingip link set dev eth1 address XX:XX:XX:XX:XX:XX.
For deeper packet inspection, refer to the official nftables NAT wiki to add logging rules to your forward chain.
Extending vs. Simplifying the Build
Not every environment needs a bare-metal Linux router. Here is how to adjust the complexity based on your actual needs.
How to Simplify: The OpenWrt Route
If maintaining systemd-networkd and nftables syntax feels like overkill, flash OpenWrt for Raspberry Pi. OpenWrt abstracts the underlying Linux networking into the LuCI web GUI. You lose some granular control over GPIO pins and custom bash scripting, but you gain a mature, web-based firewall and package manager tailored specifically for routing.
How to Extend: IDS/IPS and UPS Integration
If you want enterprise-grade features, extend the build with these two additions:
- Intrusion Detection: Install
suricata. By mirroring the WAN traffic to a virtual interface, Suricata can drop malicious payloads before they hit your LAN. Note that deep packet inspection on the Pi 5 will cap your throughput around 600-800 Mbps. - I2C UPS HAT: Wire a PiJuice or Geekworm X735 UPS HAT to GPIO 2 (SDA) and GPIO 3 (SCL). Write a Python script that polls the battery fuel gauge via I2C and triggers a graceful
shutdown -h nowwhen capacity drops below 10%, protecting your microSD card from corruption during blackouts.
FAQ: Making a Router with Raspberry Pi
Can I make a router with Raspberry Pi using just the built-in Ethernet and WiFi?
Yes, but it is only suitable for low-bandwidth IoT networks or travel routers. The built-in Ethernet is Gigabit, but the WiFi is routed through the same internal SDIO bus. If you use the built-in WiFi as the WAN (client mode) and Ethernet as the LAN, you will see high latency and jitter. For a primary home gateway, a dedicated PCIe or USB 3.0 NIC is mandatory to separate the collision domains and ensure stable throughput.
Why is my Raspberry Pi router throughput capped at 300 Mbps?
If you are using USB 3.0 Gigabit adapters, you are likely hitting the USB bot (Bulk-Only Transport) interrupt limitation. The Pi's USB host controller struggles with the massive amount of small-packet interrupts generated by gigabit traffic. Furthermore, if you have suricata or snort enabled for deep packet inspection, the Pi 5's CPU will bottleneck around 300-400 Mbps. To break the gigabit barrier, you must use a PCIe-based NIC (like the RTL8125BG) and rely on stateful firewalling (nftables) rather than deep packet inspection.
Is a Raspberry Pi router safe for a home network compared to a commercial router?
A properly configured Pi router is significantly more secure than a budget commercial router, provided you maintain it. Commercial routers often run outdated, patched-over Linux kernels with hardcoded backdoors or abandoned OEM firmware. With a Pi, you control the nftables ruleset, you receive immediate kernel security patches via apt upgrade, and you can implement strict VLAN tagging. However, the trade-off is that you are the system administrator; there is no automated "security shield" button. For authoritative hardware security baselines, consult the Raspberry Pi hardware documentation regarding secure boot and EEPROM write-protection.






