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.
| COMPONENT | WHAT | ROLE |
|---|---|---|
| Raspberry Pi 3B+ | Single-board computer | Router · DNS · firewall · DHCP |
| TP-Link TL-SG108E | 8-port managed switch ~$30 | 802.1Q VLAN trunk · device access |
| Pi-hole | DNS sinkhole software | Network-wide ad & tracker blocking |
| Unbound | Recursive DNS resolver | Full DNS resolution · no upstream dependency |
| iptables | Linux kernel firewall | NAT · packet forwarding · firewall rules |
| vlan (8021q) | Kernel module | VLAN sub-interfaces on single eth0 port |
| dnsmasq | Bundled with Pi-hole | DHCP server for all LAN devices |
| Raspberry Pi OS Lite | 64-bit Debian-based OS | Base headless system |
| UPS (recommended) | e.g. APC BE600M1 ~$80 | Keeps modem + switch + Pi alive during outages |
Use Raspberry Pi Imager. Select Raspberry Pi OS Lite (64-bit). Open advanced settings (⚙) before writing:
hostname: pihole
SSH: enabled
username: pi
password: [strong password — not default]
WiFi: do NOT configure — ethernet only
# 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# 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# 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 savenet.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.# Official installer
curl -sSL https://install.pi-hole.net | bashInstaller prompts — choose:
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 domainUnbound resolves DNS by walking the tree from root servers directly — zero reliance on third-party resolvers.
sudo apt install -y unboundserver:
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 5335In Pi-hole web UI: Settings → DNS → uncheck all upstream providers → set custom upstream to 127.0.0.1#5335
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
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.
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
# 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# 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