// open source build guide

Pi-hole as a Full Home Router
on Raspberry Pi 3B+

Convert a Raspberry Pi 3B+ into a full network router with DNS-level ad blocking, recursive DNS resolution, NAT, and DHCP — using a single ethernet port and 802.1Q VLAN trunking over a managed switch.

Raspberry Pi 3B+ 802.1Q VLANs Pi-hole Unbound iptables NAT Gigabit
// table of contents
// network topology diagram
ISP / INTERNET
coax · dsl · fiber
MODEM
your existing modem
public WAN IP from ISP
ethernet → port 1
MANAGED SWITCH
TP-Link TL-SG108E · 802.1Q VLANs
Port 1: VLAN 10 tagged (WAN — modem)
Port 2: VLAN 10+20 tagged (trunk — Pi)
Ports 3–8: VLAN 20 untagged (LAN devices)
trunk · tagged · port 2
RASPBERRY PI 3B+
eth0 · single cable · dual VLAN sub-interfaces
eth0.10 = WAN · gets modem IP via DHCP
eth0.20 = LAN · 192.168.2.1 gateway
Pi-hole (DNS + DHCP) · Unbound · iptables NAT
VLAN 20 untagged · port 3
WIFI ACCESS POINT
any router in bridge mode · DHCP disabled
static IP 192.168.2.2 · DNS 192.168.2.1
📱 Phone
💻 Laptop
📺 Smart TV
🖥️ Desktop
🎮 Console
🔌 IoT
// hardware & software stack
COMPONENTWHATROLE
Raspberry Pi 3B+Single-board computerRouter · DNS · firewall · DHCP
TP-Link TL-SG108E8-port managed switch ~$30802.1Q VLAN trunk · device access
Pi-holeDNS sinkhole softwareNetwork-wide ad & tracker blocking
UnboundRecursive DNS resolverFull DNS resolution · no upstream dependency
iptablesLinux kernel firewallNAT · packet forwarding · firewall rules
vlan (8021q)Kernel moduleVLAN sub-interfaces on single eth0 port
dnsmasqBundled with Pi-holeDHCP server for all LAN devices
Raspberry Pi OS Lite64-bit Debian-based OSBase headless system
UPS (recommended)e.g. APC BE600M1 ~$80Keeps modem + switch + Pi alive during outages
// step-by-step setup
01
Flash Raspberry Pi OS Lite (64-bit)
headless · no desktop · minimum RAM usage

Use Raspberry Pi Imager. Select Raspberry Pi OS Lite (64-bit). Open advanced settings (⚙) before writing:

imager advanced settings
hostname: pihole SSH: enabled username: pi password: [strong password — not default] WiFi: do NOT configure — ethernet only
Do not use Raspberry Pi OS Desktop — it wastes RAM the router stack needs.
02
First Boot & System Update
SSH in · update · install VLAN tools
# SSH in ssh pi@pihole.local # Update all packages sudo apt update && sudo apt full-upgrade -y # Install required tools sudo apt install -y vlan iptables iptables-persistent curl # Load 802.1Q kernel module now sudo modprobe 8021q # Persist on boot echo "8021q" | sudo tee -a /etc/modules
03
Configure VLAN Interfaces
eth0.10 = WAN · eth0.20 = LAN · single cable
One physical ethernet cable carries both VLANs via 802.1Q tagging. The managed switch separates them — no second NIC needed.
/etc/network/interfaces
# Loopback auto lo iface lo inet loopback # Physical port — no IP, carries tagged frames auto eth0 iface eth0 inet manual # WAN — VLAN 10 — gets IP from modem auto eth0.10 iface eth0.10 inet dhcp vlan-raw-device eth0 # LAN — VLAN 20 — Pi is the gateway auto eth0.20 iface eth0.20 inet static address 192.168.2.1 netmask 255.255.255.0 vlan-raw-device eth0
sudo systemctl restart networking
04
Enable IP Forwarding + NAT
this is what makes it an actual router
# Enable IP forwarding — persist across reboots echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p # NAT — masquerade LAN traffic out WAN sudo iptables -t nat -A POSTROUTING -o eth0.10 -j MASQUERADE # Allow return traffic from WAN to LAN sudo iptables -A FORWARD -i eth0.10 -o eth0.20 \ -m state --state RELATED,ESTABLISHED -j ACCEPT # Allow LAN to initiate connections out sudo iptables -A FORWARD -i eth0.20 -o eth0.10 -j ACCEPT # Save rules — survives reboot sudo netfilter-persistent save
Without net.ipv4.ip_forward=1 the Pi receives packets but drops them instead of routing. This single line is what separates a router from a regular Linux host.
05
Install Pi-hole
DNS sinkhole + DHCP server
# Official installer curl -sSL https://install.pi-hole.net | bash

Installer prompts — choose:

pi-hole installer selections
Interface: eth0.20 ← LAN interface only Upstream DNS: 127.0.0.1#5335 ← Unbound (set up next) Block lists: default is fine to start Admin web UI: Yes Log queries: your preference Privacy mode: 0 (show all)
# Enable Pi-hole DHCP (web UI: Settings → DHCP, or CLI): sudo pihole -a enabledhcp 192.168.2.10 192.168.2.254 192.168.2.1 24 pihole.local # range-start range-end gateway prefix domain
DHCP range 192.168.2.10–254 · Gateway 192.168.2.1 (Pi) · Domain pihole.local
06
Install Unbound
recursive DNS · no Google · no Cloudflare

Unbound resolves DNS by walking the tree from root servers directly — zero reliance on third-party resolvers.

sudo apt install -y unbound
/etc/unbound/unbound.conf.d/pi-hole.conf
server: verbosity: 0 interface: 127.0.0.1 port: 5335 do-ip4: yes do-udp: yes do-tcp: yes do-ip6: no # Security harden-glue: yes harden-dnssec-stripped: yes use-caps-for-id: no edns-buffer-size: 1472 # Privacy hide-identity: yes hide-version: yes # Cache cache-min-ttl: 0 cache-max-ttl: 86400 prefetch: yes num-threads: 1 # Root hints root-hints: "/var/lib/unbound/root.hints"
# Download root hints sudo wget -O /var/lib/unbound/root.hints \ https://www.internic.net/domain/named.root sudo systemctl restart unbound # Verify dig google.com @127.0.0.1 -p 5335

In Pi-hole web UI: Settings → DNS → uncheck all upstream providers → set custom upstream to 127.0.0.1#5335

07
Configure Managed Switch VLANs
TP-Link TL-SG108E · web UI · 802.1Q
Configure the switch BEFORE connecting the modem or Pi. Access the switch web UI by connecting a laptop directly — default IP is usually 192.168.0.1.
switch port assignment — TL-SG108E
Port 1: VLAN 10 tagged ← modem (WAN uplink) Port 2: VLAN 10+20 tagged ← Raspberry Pi (trunk) Port 3: VLAN 20 untagged ← WiFi access point Port 4: VLAN 20 untagged ← wired device Port 5: VLAN 20 untagged ← wired device Port 6: VLAN 20 untagged ← wired device Port 7: VLAN 20 untagged ← wired device Port 8: VLAN 20 untagged ← wired device
Port 2 (Pi trunk) MUST be tagged for BOTH VLANs. Device ports are untagged VLAN 20 only — they never see VLAN 10 (WAN) traffic.
08
Configure WiFi Access Point
any router · bridge mode · DHCP off

Your existing router becomes a dumb WiFi access point. Pi-hole handles all routing and DHCP. Works with any router brand that supports bridge or AP mode.

access point settings (any router)
Mode: Bridge Mode (or AP mode / disable router) DHCP Server: Disabled Static IP: 192.168.2.2 (within Pi's LAN subnet) Gateway: 192.168.2.1 (Pi-hole) DNS: 192.168.2.1 (Pi-hole) WiFi SSID: your existing network name WiFi pass: unchanged
In bridge mode the router passes all traffic to the Pi. Wireless devices get IPs and DNS from Pi-hole automatically — full filtering on both WiFi and wired.
09
Verify Everything Works
routing · DNS · blocking · DHCP
# Check WAN got an IP from modem ip addr show eth0.10 # Check LAN interface ip addr show eth0.20 # Check routing table — default route via eth0.10 ip route show # Ping internet from Pi ping -c 3 8.8.8.8 # Test Unbound recursive resolution dig google.com @127.0.0.1 -p 5335 # Test Pi-hole blocking — should return 0.0.0.0 dig doubleclick.net @192.168.2.1
# From any device on the network: # Gateway should be 192.168.2.1 ip route # Linux netstat -rn # macOS # DNS should be 192.168.2.1 cat /etc/resolv.conf # Pi-hole admin dashboard http://192.168.2.1/admin
10
Harden & Maintain
watchdog · UPS · auto-updates · blocklists
# Hardware watchdog — auto-reboot on freeze sudo apt install -y watchdog echo "bcm2835_wdt" | sudo tee -a /etc/modules sudo systemctl enable watchdog --now # Unattended security upgrades sudo apt install -y unattended-upgrades sudo dpkg-reconfigure unattended-upgrades # Update Pi-hole binary pihole -up # Update blocklists pihole -g # Schedule weekly gravity update — 3am Sunday echo "0 3 * * 0 root pihole -g" | sudo tee /etc/cron.d/pihole-gravity
If the Pi loses power your entire network loses internet. Plug the modem, switch, and Pi all into a UPS to keep the full stack alive. The watchdog handles software freezes; the UPS handles power loss — you need both.
Watchdog works by having the OS constantly reset a hardware timer. If the system freezes and stops resetting it, the timer expires and forces a hard reboot — fully automatic, no intervention needed.