Pi-hole: Network-Wide Ad Blocker Setup Guide

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.

Contents
  1. Prerequisites
  2. How Pi-hole Works
  3. Step 1: Assign a Static IP
  4. Step 2: Install Pi-hole
  5. Step 3: Access the Web Interface
  6. Step 4: Configure Your Router
  7. Step 5: Add Blocklists
  8. Step 6: Whitelist and Blacklist Domains
  9. Step 7: Pi-hole with Unbound (Recursive DNS)
  10. Running Pi-hole in Docker
    1. Further Reading

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 IP

The 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.yaml
network:
  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 eth0

On Raspberry Pi OS, static IPs are set via /etc/dhcpcd.conf:

sudo nano /etc/dhcpcd.conf
interface 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.1
sudo systemctl restart dhcpcd

Step 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.conf

Now run the official installer:

curl -sSL https://install.pi-hole.net | bash

The 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 enabled

Step 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 Server

Set 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 working

Step 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
ListURLSizeWhat it blocks
StevenBlack (default)raw.githubusercontent.com/StevenBlack/hosts/master/hosts~145kAds, malware
Hagezi Proraw.githubusercontent.com/hagezi/dns-blocklists/main/domains/pro.txt~600kAds, tracking, telemetry
OISD Bigbig.oisd.nl~280kAds, tracking — well-maintained, low false positives
Hagezi Threat Intelligenceraw.githubusercontent.com/hagezi/dns-blocklists/main/domains/tif.txt~900kMalware, 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 -l

Common 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 checks

When 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 A

Now 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 -


Go up