Pi-hole, Unbound and PiVPN (WireGuard) installation guide#
This guide documents a clean, battle-tested reinstall of a Raspberry Pi DNS + VPN stack:
- Pi-hole – network-wide DNS filtering
- Unbound – recursive DNS resolver
- PiVPN (WireGuard) – secure remote access using your home DNS
It is written to avoid common pitfalls (DNS proxy issues, premature DHCP changes, routing loops).
0. Assumptions#
- Raspberry Pi 4
- Raspberry Pi OS (64-bit, Lite or Desktop)
- Ethernet connection
- Static LAN IP via DHCP reservation (example:
192.168.1.20) - Router supports:
- Static DNS via DHCP
- Port forwarding (UDP)
1. Base OS Preparation#
Flash OS#
Use Raspberry Pi Imager:
- OS: Raspberry Pi OS (64-bit) - Desktop or Lite
- Enable SSH
- Set user + password
- (Optional) Configure Wi-Fi as fallback
- Set locale + timezone
Boot and connect over ssh (e.g. ssh pi@ipaddress).
2. System Update#
sudo apt update
sudo apt full-upgrade -y
sudo reboot3. Locale (Hard-coded, permanent)#
In my case, the locale was not set up correclty and gave a warning when logging in over ssh. To fix and check this, run teh following commands.
sudo locale-gen en_GB.UTF-8Edit:
sudo nano /etc/default/localeLANG=en_GB.UTF-8
LC_CTYPE=en_GB.UTF-8
LC_ALL=en_GB.UTF-8Ensure /etc/environment contains no LC_* or LANG overrides.
Reboot.
Verify:
locale4. Pi-hole Installation#
curl -sSL https://install.pi-hole.net | bashInstaller choices#
- Interface:
eth0 - Static IP: accept current
- Upstream DNS: temporary (Cloudflare / Quad9)
- Web UI: yes
- Web server: yes
Verify:
pihole status5. Unbound (Recursive DNS)#
Install#
sudo apt install unbound -yConfig#
sudo nano /etc/unbound/unbound.conf.d/pi-hole.confserver:
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
root-hints: "/var/lib/unbound/root.hints"
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: yes
edns-buffer-size: 1232
prefetch: yes
num-threads: 1
cache-min-ttl: 3600
cache-max-ttl: 86400
hide-identity: yes
hide-version: yesRoot hints#
sudo curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
sudo chown unbound:unbound /var/lib/unbound/root.hintsStart#
sudo systemctl enable unbound
sudo systemctl restart unboundTest#
dig . @127.0.0.1 -p 53356. Connect Pi-hole → Unbound#
Pi-hole UI#
- Settings → DNS
- Disable all upstream DNS providers
- Custom IPv4 DNS:
127.0.0.1#5335
Prevent fallback#
sudo nano /etc/dnsmasq.d/99-disable-resolv.confno-resolvRestart DNS:
pihole restartdns reloadVerify in Query Log:
- Upstream:
localhost#5335
7. Router DNS (CRITICAL ORDER)#
⚠️ Do this only after Pi-hole + Unbound work
Router (LAN / DHCP settings):
- DNS mode: Static
- Primary DNS:
192.168.1.20 - Secondary DNS: (empty)
Reboot router.
Verify from a client:
nslookup google.comServer should be 192.168.1.20.
8. PiVPN (WireGuard)#
Install#
curl -L https://install.pivpn.io | bashInstaller choices#
- VPN: WireGuard
- Interface:
eth0 - Port:
51820 - DNS provider: Custom → 192.168.1.20
- Enable unattended upgrades
Create client#
pivpn addExport#
pivpn -qr9. Router Port Forward#
Forward:
UDP 51820 → 192.168.1.2010. VPN Test#
From mobile data / outside LAN:
- Enable WireGuard
- Visit https://dnsleaktest.com
- Confirm only home IP DNS
Pi-hole dashboard should show VPN client (10.6.0.x).
11. Known Pitfalls (Avoid These)#
- ❌ Do NOT enable router DNS Proxy
- ❌ Do NOT change DHCP DNS before Pi-hole works
- ❌ Do NOT use
127.0.0.1as DNS for VPN clients - ❌ Do NOT mix static IP on Pi + DHCP reservation
12. Final Architecture#
Clients
↓
Router (DHCP)
↓
Pi-hole :53
↓
Unbound :5335
↓
Root DNS serversVPN clients:
Remote Device
↓ WireGuard
PiVPN
↓
Pi-hole → Unbound13. Maintenance#
Update regularly:
sudo apt update && sudo apt full-upgrade -y
pihole -upDebug:
pivpn debugEnd#
This setup provides:
- Full DNS privacy
- Network-wide filtering
- Secure remote access
- No dependency on third-party DNS providers