Pi-hole: Network-Wide Ad Blocker Setup Guide

Tested on: Ubuntu 26.04 LTS · Debian 12 · Raspberry Pi OS (Bookworm) — Last updated: June 2026
Pi-hole is a DNS sinkhole that blocks ads and tracking domains at the network level before they ever reach a device. Every phone, smart TV, laptop, and IoT gadget on your network gets ad blocking automatically — no browser extensions, no per-device configuration. This guide covers a full production setup: static IP, installation, router configuration, curated blocklists, the Unbound recursive resolver, Docker deployment, and the commands you'll actually use day to day.
Prerequisites
- A Linux machine with a static IP address on your LAN — a Raspberry Pi 3/4/5, an always-on mini PC, or a dedicated VM all work well
- Ubuntu 24.04+, Debian 13+, or Raspberry Pi OS (Bookworm). Any systemd-based distro is fine.
- Minimum hardware: 512 MB RAM, 4 GB disk. Pi-hole's gravity database grows over time; 8 GB+ is comfortable.
- Root or sudo access
- Your router's admin credentials — you'll need to change its DNS settings after installation
How Pi-hole Works
Pi-hole replaces your router's DNS resolver for all devices on the network. Every DNS query passes through it first:
# Without Pi-hole:
Device → "ads.doubleclick.net?" → ISP DNS → returns IP → ad loads
# With Pi-hole:
Device → "ads.doubleclick.net?" → Pi-hole →
[blocklist match] → returns 0.0.0.0 → request dies, ad never loads
[no match] → forwards to upstream (Cloudflare, Google, Unbound) → returns real IPThe key advantage over browser-level blockers: Pi-hole works on every device including those where you can't install extensions — Android apps, Apple TV, smart home sensors, game consoles. The tradeoff is that it can only block at the domain level, not by URL path or element, so some ad-funded sites may still show blank spaces where ads were.
Step 1: Assign a Static IP
Pi-hole's IP must never change — your router will be hardcoded to use it as DNS. The cleanest approach on Ubuntu 26.04 is Netplan:
# Find your interface name and current IP:
ip addr show | grep "inet "
# Edit Netplan config:
sudo nano /etc/netplan/01-netcfg.yamlnetwork:
version: 2
ethernets:
eth0: # replace with your interface name
dhcp4: no
addresses: [192.168.1.10/24] # choose an IP outside your router's DHCP range
routes:
- to: default
via: 192.168.1.1 # your router's IP
nameservers:
addresses: [8.8.8.8, 1.1.1.1]sudo netplan apply
# Verify the IP stuck:
ip addr show eth0On Raspberry Pi OS, static IPs are set via /etc/dhcpcd.conf:
sudo nano /etc/dhcpcd.confinterface eth0
static ip_address=192.168.1.10/24
static routers=192.168.1.1
static domain_name_servers=8.8.8.8 1.1.1.1sudo systemctl restart dhcpcdStep 2: Install Pi-hole
Before running the installer on Ubuntu 26.04, disable systemd-resolved — it occupies port 53 and will conflict with Pi-hole's DNS listener:
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.confNow run the official installer:
curl -sSL https://install.pi-hole.net | bashThe interactive installer walks through several choices:
- Upstream DNS provider: Choose Cloudflare (1.1.1.1) or Google (8.8.8.8) for now — you can switch to Unbound later
- Default blocklists: Accept the defaults (StevenBlack hosts list). You'll add more after install.
- Web admin interface: Yes — you need this
- Query logging: Yes — essential for diagnosing breakage
- Privacy mode: Level 0 (show everything) is most useful while tuning
At the end, the installer prints a randomly generated admin password. Save it. If you miss it:
# Reset the admin password:
pihole -a -p yournewpassword
# Set an empty password (no login required — fine for a trusted LAN):
pihole -a -p
# Verify Pi-hole services are running:
pihole status# Expected output:
[✓] FTL is listening on port 53
[✓] Pi-hole blocking is enabledStep 3: Access the Web Interface
Open http://192.168.1.10/admin (your Pi-hole's IP) in a browser. Once you've pointed your router's DNS to Pi-hole, http://pi.hole/admin also works.
The dashboard gives you a live view of everything happening on your network's DNS layer:
- Queries today / Queries blocked: expect 15–30% block rate with default lists; higher with additional lists
- Top Blocked Domains: useful for understanding what your devices are phoning home to
- Top Clients: see which device generates the most traffic — smart TVs and Android phones are usually the noisiest
- Query Log (Tools → Query Log): the single most useful view when something breaks
Step 4: Configure Your Router
Point your router's DHCP server to hand out Pi-hole's IP as the DNS server for all devices. The exact menu path varies by router firmware:
# General location across common router firmware:
# Asus Merlin: LAN → DHCP Server → DNS Server 1
# FRITZ!Box: Home Network → Network → DNS Rebind Protection
# DD-WRT: Setup → Basic Setup → Static DNS 1
# OpenWrt: Network → DHCP and DNS → DNS forwardings
# pfSense: Services → DHCP Server → DNS Servers
# Unifi: Networks → [your network] → DHCP → DNS ServerSet Primary DNS to your Pi-hole IP (e.g., 192.168.1.10). For the secondary DNS, leave it blank or use a real upstream (8.8.8.8) as a fallback — note that a fallback secondary means some DNS queries will bypass Pi-hole if it's unreachable, so devices may occasionally see ads during Pi-hole downtime. That's an acceptable tradeoff for reliability.
After saving router settings, devices need to renew their DHCP lease before they'll use Pi-hole. Fastest method: disconnect and reconnect to WiFi, or run sudo dhclient -r && sudo dhclient on Linux machines.
Confirm a device is using Pi-hole:
nslookup doubleclick.net 192.168.1.10
# Should return 0.0.0.0 if Pi-hole is workingStep 5: Add Blocklists
The default StevenBlack list (~145k domains) is a solid baseline. These additional lists cover more tracking, telemetry, and threat domains without causing excessive breakage:
# Add lists via: Admin → Group Management → Adlists → Add new list
# After adding all lists, update gravity to download and compile them:
pihole -g| List | URL | Size | What it blocks |
|---|---|---|---|
| StevenBlack (default) | raw.githubusercontent.com/StevenBlack/hosts/master/hosts | ~145k | Ads, malware |
| Hagezi Pro | raw.githubusercontent.com/hagezi/dns-blocklists/main/domains/pro.txt | ~600k | Ads, tracking, telemetry |
| OISD Big | big.oisd.nl | ~280k | Ads, tracking — well-maintained, low false positives |
| Hagezi Threat Intelligence | raw.githubusercontent.com/hagezi/dns-blocklists/main/domains/tif.txt | ~900k | Malware, phishing, C2 servers |
Don't add every list you find — larger gravity databases increase memory usage and raise the chance of false positives that break legitimate sites. Hagezi Pro + OISD is a well-balanced combination for most home networks.
Step 6: Whitelist and Blacklist Domains
# Whitelist a domain (stop blocking it):
pihole -w example.com
# Whitelist via regex (match all subdomains):
pihole --white-regex '.*.example.com$'
# Blacklist a domain (block it even if not on a list):
pihole -b telemetry.somedomain.com
# Remove from whitelist:
pihole -w -d example.com
# List current whitelisted domains:
pihole -w -lCommon domains that get blocked by aggressive lists and need whitelisting:
pihole -w graph.facebook.com # Facebook features, login via FB
pihole -w spclient.wg.spotify.com # Spotify playback
pihole -w login.microsoftonline.com # Microsoft 365, Azure AD login
pihole -w api.twitter.com # X/Twitter embeds
pihole -w clients4.google.com # Chrome update checksWhen something breaks, the fastest debugging workflow is: open Pi-hole's Query Log, reproduce the breakage, and look for red (blocked) entries with timestamps matching the moment things broke. Whitelist the blocked domain and test again.
Step 7: Pi-hole with Unbound (Recursive DNS)
By default Pi-hole forwards your DNS queries to Google or Cloudflare — you're trusting a third party with your browsing history. Unbound eliminates this by resolving DNS recursively from the root nameservers directly, on your machine. Your queries never leave your network except to the authoritative nameservers for each domain.
sudo apt install unbound -y
sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf << 'EOF'
server:
verbosity: 0
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-udp: yes
do-tcp: yes
do-ip6: no
prefer-ip6: no
# Security hardening:
harden-glue: yes
harden-dnssec-stripped: yes
harden-referral-path: yes
use-caps-for-id: no
edns-buffer-size: 1232
# Performance:
prefetch: yes
prefetch-key: yes
num-threads: 1
so-rcvbuf: 1m
cache-min-ttl: 300
cache-max-ttl: 86400
# Block RFC1918 addresses in responses (rebinding protection):
private-address: 192.168.0.0/16
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 127.0.0.0/8
EOF
sudo systemctl restart unbound
# Test that Unbound resolves correctly:
dig @127.0.0.1 -p 5335 google.com ANow configure Pi-hole to use Unbound: Admin → Settings → DNS, uncheck all upstream providers, and enter 127.0.0.1#5335 as a custom upstream DNS server. Save and test:
dig @192.168.1.10 cloudflare.com A
# Output should show your Pi-hole IP as the server and a real IP for cloudflare.com
# ;; SERVER: 192.168.1.10#53(192.168.1.10)Running Pi-hole in Docker
If Pi-hole shares a host with other services, Docker keeps its dependencies isolated. Save this as docker-compose.yml:
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
environment:
TZ: 'Europe/London'
WEBPASSWORD: 'changeme'
PIHOLE_DNS_: '127.0.0.1#5335' # point to Unbound if running it too
DNSMASQ_LISTENING: 'all'
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
cap_add:
- NET_ADMIN
restart: unless-stopped# Disable systemd-resolved first (see Step 2), then:
docker compose up -
