Turn a Linux Box into a Router
Configure a Linux machine as a full router: static interfaces, kernel IP forwarding, nftables NAT, dnsmasq DHCP, and DNS forwarding — step by step.
Before you start
- ▸Two or more network interfaces (physical or virtual) installed and visible to the OS
- ▸Root or sudo access on the machine
- ▸The machine connected to an upstream modem or ISP device on the WAN interface
- ▸Basic familiarity with Linux networking concepts (IP addresses, subnets, gateways)
Turning a spare Linux machine into a router gives you full control over your network: custom firewall rules, DNS filtering, traffic shaping, and more — without proprietary firmware. This guide walks through every layer: interface setup, kernel IP forwarding, NAT with nftables, a DHCP server, and a caching DNS forwarder. You need at least two network interfaces (the WAN-facing NIC and one or more LAN-facing NICs).
1. Identify and Name Your Interfaces
Modern kernels use predictable interface names. Confirm what you have:
ip link show
Decide which interface is WAN (upstream, e.g. from your ISP modem) and which is LAN (downstream, to your switch or devices). Throughout this guide WAN=enp1s0 and LAN=enp2s0 are used as examples — substitute your real names.
2. Configure Network Interfaces
Give the LAN interface a static IP. The WAN interface usually gets its address from your ISP via DHCP. Configure with systemd-networkd, which is available on every major distro.
Enable systemd-networkd
sudo systemctl enable --now systemd-networkd
WAN interface (DHCP from ISP)
sudo tee /etc/systemd/network/10-wan.network <<'EOF'
[Match]
Name=enp1s0
[Network]
DHCP=yes
DNS=127.0.0.1
EMitLLDP=no
IPForward=yes
EOF
LAN interface (static)
sudo tee /etc/systemd/network/20-lan.network <<'EOF'
[Match]
Name=enp2s0
[Network]
Address=192.168.10.1/24
IPForward=yes
ConfigureWithoutCarrier=yes
EOF
sudo networkctl reload
Verify both interfaces are configured:
networkctl status enp1s0 enp2s0
3. Enable Kernel IP Forwarding
By default the kernel drops packets that arrive on one interface but are destined for another. You must enable forwarding persistently.
sudo tee /etc/sysctl.d/99-router.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sudo sysctl --system
Confirm it took effect (should print 1):
sysctl net.ipv4.ip_forward
4. Set Up NAT and the Firewall with nftables
nftables is the modern replacement for iptables/ip6tables/arptables and is the default on Debian 11+, Fedora 32+, and RHEL 9+. The ruleset below implements masquerade NAT on the WAN interface and a stateful firewall that forwards established traffic, blocks unsolicited inbound on WAN, and allows everything from LAN.
Install nftables
# Debian/Ubuntu
sudo apt install nftables
# Fedora/RHEL/Rocky
sudo dnf install nftables
# Arch
sudo pacman -S nftables
Write the ruleset
sudo tee /etc/nftables.conf <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
define WAN = enp1s0
define LAN = enp2s0
define LAN_NET = 192.168.10.0/24
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iifname lo accept
ct state established,related accept
# Allow ICMP
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Allow all from LAN
iifname $LAN accept
# Allow SSH on WAN (remove or restrict after setup)
iifname $WAN tcp dport 22 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
iifname $LAN oifname $WAN accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat;
oifname $WAN masquerade
}
}
EOF
sudo systemctl enable --now nftables
sudo nft -f /etc/nftables.conf
Check the loaded ruleset:
sudo nft list ruleset
5. DHCP Server with dnsmasq
dnsmasq handles both DHCP and DNS forwarding in a single lightweight daemon — ideal for a home or small-office router. Install it first.
# Debian/Ubuntu
sudo apt install dnsmasq
# Fedora/RHEL/Rocky
sudo dnf install dnsmasq
# Arch
sudo pacman -S dnsmasq
Configure dnsmasq
Back up the default config, then write a clean one:
sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.bak
sudo tee /etc/dnsmasq.conf <<'EOF'
# Listen only on the LAN interface
interface=enp2s0
bind-interfaces
# DHCP pool: hand out .100-.200, 24h lease
dhcp-range=192.168.10.100,192.168.10.200,24h
dhcp-option=option:router,192.168.10.1
dhcp-option=option:dns-server,192.168.10.1
# Optional: reserve an address by MAC
# dhcp-host=aa:bb:cc:dd:ee:ff,192.168.10.50,myserver
# DNS: forward upstream queries
# Use your ISP's DNS or a public resolver
server=1.1.1.1
server=9.9.9.9
# Cache 512 entries
cache-size=512
# Don't read /etc/resolv.conf for upstream servers
no-resolv
# Local domain
domain=lan
local=/lan/
expand-hosts
EOF
sudo systemctl enable --now dnsmasq
If dnsmasq conflicts with systemd-resolved (common on Ubuntu), disable resolved first:
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
echo 'nameserver 127.0.0.1' | sudo tee /etc/resolv.conf
6. Verify End-to-End Connectivity
Connect a client to the LAN side (directly or via switch). The client should receive a DHCP lease in the 192.168.10.100–200 range with gateway and DNS pointing to 192.168.10.1.
On the router
# Confirm DHCP leases
cat /var/lib/misc/dnsmasq.leases
# Check NAT translation is happening
sudo nft list table ip nat
# Watch forwarded packets in real time
sudo nft monitor trace # Ctrl-C to stop
On the client
ip route show
ping -c3 192.168.10.1
ping -c3 1.1.1.1
dig @192.168.10.1 example.com
7. Persist Everything Across Reboots
All three services must start in the right order. systemd handles this automatically because dnsmasq has After=network-online.target in its unit, but double-check:
sudo systemctl is-enabled systemd-networkd nftables dnsmasq
All three should report enabled. Reboot and re-run the client-side verification above.
Troubleshooting
- Client gets no DHCP lease: Check
sudo journalctl -u dnsmasq -n 50. Confirm dnsmasq is bound to the correct interface and that nftables is not blocking UDP 67/68 on the LAN input chain (the ruleset above allows all LAN traffic, so this should be fine). - Client can ping 192.168.10.1 but not the internet: IP forwarding may not be active (
sysctl net.ipv4.ip_forwardmust return1). Also verify the forward chain in nftables allows LAN→WAN and that the nat postrouting masquerade rule is loaded. - nft: command not found after package install: On RHEL/Rocky 8, the package is
nftablesbut the binary path may need a shell reload: runhash -ror open a new terminal. - WAN interface not getting a DHCP address: Some ISPs lock by MAC. If you replaced an existing router, clone its MAC:
ip link set enp1s0 address AA:BB:CC:DD:EE:FFand add a permanentMACAddress=line to your10-wan.networkfile. - dnsmasq fails to start — address already in use: Another process (often systemd-resolved or NetworkManager's internal dnsmasq) is holding port 53. Check with
sudo ss -tulpn | grep ':53'and disable the conflicting service.
Frequently asked questions
- Can I use this setup with a USB-to-Ethernet adapter as a second NIC?
- Yes, USB NICs work, but they have higher latency and CPU overhead and can be unreliable under sustained load. For a production router use a PCIe NIC. If you do use USB, pin the interface name with a udev rule so it doesn't change between reboots.
- Does this guide support IPv6 routing?
- Kernel forwarding for IPv6 is enabled in step 3, and the nftables ruleset accepts ICMPv6. Full IPv6 with prefix delegation from your ISP requires adding DHCPv6-PD config to your WAN network unit and advertising prefixes on the LAN — a topic that warrants its own guide.
- Why use dnsmasq instead of ISC DHCP + BIND?
- dnsmasq integrates DHCP and DNS caching in one small daemon, requires minimal config, and automatically inserts DHCP hostnames into its DNS answers. ISC DHCP + BIND is appropriate for large networks where you need split-horizon DNS, dynamic updates, or DNSSEC signing.
- How do I block specific sites or ad domains at the router level?
- dnsmasq supports block lists via 'address=/doubleclick.net/#' entries or by pointing to a hosts-format block list with the 'addn-hosts' directive. Tools like Pi-hole use this same mechanism and can replace the dnsmasq DNS function if you want a web UI.
- Will nftables rules survive if I also have firewalld or ufw installed?
- No. firewalld and ufw both manage nftables or iptables rules independently. Running them alongside a manual /etc/nftables.conf will cause conflicts. Disable and mask firewalld or ufw before using the approach in this guide: 'sudo systemctl disable --now firewalld'.
Related guides
Build a Mesh VPN with Nebula
Build a fully self-hosted mesh VPN with Nebula: create a CA, sign node certs, configure lighthouses, enforce group-based firewall rules, and run as a systemd service.
Common Linux Network Ports Reference
Learn Linux port ranges, read /etc/services, find what's listening with ss and nmap, and apply solid firewall rules to expose or block the right ports.
How to Configure a Static IP on Linux
Configure a static IP on Linux using Netplan, NetworkManager (nmcli), or systemd-networkd across Ubuntu, Fedora, Debian, and Arch with verified steps.
Expose a Service with Cloudflare Tunnel
Expose local services to the internet without port-forwarding using Cloudflare Tunnel. Install cloudflared, create a named tunnel, configure ingress rules, and run as a systemd service.