Network-Wide Ad Blocking with Pi-hole
Block ads and trackers for every device on your network at the DNS level, with no per-device app or browser extension, by making Pi-hole your network's DNS server.
What you'll build and why
Pi-hole becomes your network's DNS server: when a device asks "what's the IP address for this ad-serving domain," Pi-hole checks it against a blocklist and, if it matches, refuses to resolve it -- the ad or tracker never loads, because the device never gets an address to connect to. Every device on the network gets this automatically: phones, smart TVs, game consoles, anything that does DNS lookups, with nothing installed on the device itself.
Don't build this if: you only want to block ads in one browser on one device -- a browser extension (uBlock Origin, for example) is simpler and blocks some things DNS-level filtering can't (in-page ad placements that don't come from a separately-resolved domain). Pi-hole is worth it specifically because it covers devices you can't install an extension on -- a smart TV, a game console, a guest's phone on your Wi-Fi.
This guide gets Pi-hole running and your network pointed at it. It doesn't cover running Pi-hole as your DHCP server too (a separate, more involved setup with real conflict risk against your router's own DHCP -- see Go further) or running two Pi-holes for redundancy (also Go further).
How it works
Device (phone, TV, etc.)
│ DNS query
▼
Router -- hands out Pi-hole's IP as "the" DNS server, via DHCP
│ DNS query
▼
Pi-hole -- checks the domain against the blocklist
│
├── Matches the blocklist:
│ returns "no address" (or 0.0.0.0) --
│ the ad/tracker request never goes anywhere
│
└── Doesn't match:
│ DNS query
▼
Upstream resolver (Cloudflare, Quad9, etc.)
-- returns the real answer, same as
any other DNS resolver would
The device never talks to Pi-hole about anything except DNS -- it doesn't proxy your actual web traffic, so it can't see or filter the content of a page, only which domains get resolved at all. That's both the limitation (it can't block an ad served from the same domain as legitimate content) and the reason it's lightweight and doesn't slow down browsing.
Before you start
Decision: where does Pi-hole run?
Pi-hole needs to be something that's always on, since every device's DNS resolution depends on it being reachable. This guide uses Docker on an existing always-on machine (fastest path if you already run Docker somewhere); a dedicated Raspberry Pi is equally valid and uses the identical Docker steps below, just on smaller hardware.
Before touching your router: note your router's current DNS setting (usually "Automatic" or your ISP's servers) so you can revert quickly if something goes wrong during setup.
Steps
Step 1: Install Docker (skip if you already have it)
$ curl -fsSL https://get.docker.com | sh
Docker's own official install script, safe for Debian/Ubuntu-based systems including Raspberry Pi OS. For anything else, use the official install docs for your distribution.
Step 2: Run Pi-hole with Docker Compose
$ mkdir -p ~/pihole && cd ~/pihole
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
- "443:443/tcp"
environment:
TZ: "America/New_York"
FTLCONF_webserver_api_password: "<YOUR-ADMIN-PASSWORD>"
volumes:
- "./etc-pihole:/etc/pihole"
restart: unless-stopped
Set TZ to your own time zone and replace <YOUR-ADMIN-PASSWORD> with a real password. If you skip setting a password, Pi-hole generates a random one for you rather than falling back to a known default -- check the container's logs (docker logs pihole) for it if you go that route, but setting your own is simpler.
$ docker compose up -d
Expected output: docker ps shows the pihole container as Up.
Step 3: Log into the admin UI and set an upstream resolver
Visit http://<pi-hole-host-ip>/admin and log in with your password. Settings → DNS, pick an upstream resolver for anything that isn't blocked -- Cloudflare (1.1.1.1), Quad9 (9.9.9.9), or Google (8.8.8.8) are all reasonable. Pi-hole ships with a reputable default blocklist already active; you can add more from firebog.net once the basics work.
Step 4: Point your network at Pi-hole
The clean way is through your router, not per-device: log into your router's admin page and set its DHCP DNS server (sometimes labeled just "DNS") to Pi-hole's host IP. Every device that gets an address from your router via DHCP now uses Pi-hole automatically, including ones that join later.
If you'd rather test on one device first, most OSes let you set DNS manually per-device or per-network before committing to the router-wide change.
Verify it works
-
http://<pi-hole-host-ip>/adminloads and shows the dashboard - From a device pointed at Pi-hole, a known ad-serving domain (e.g.
doubleclick.net) fails to resolve, while an ordinary site loads normally - The admin dashboard's Query Log shows real queries from your devices, some marked blocked
- Failure test: stop the Pi-hole container (
docker stop pihole). Confirm devices pointed at it as their only DNS server lose name resolution entirely (this is expected -- it's why Go further covers a second instance for redundancy, and why you shouldn't point every device at a single Pi-hole with no fallback until you've added one). Restart it (docker start pihole) and confirm resolution comes back within a few seconds.
Secure it
- What's exposed: the admin UI (port 80, and 443 with a self-signed certificate Pi-hole generates itself) and DNS (port 53, TCP+UDP) on whatever interface Docker binds them to. This guide assumes LAN-only reachability -- don't port-forward 53 or 80/443 to the internet. An open DNS resolver reachable from the internet is a known vector for DNS-amplification attacks (an attacker sends it small spoofed queries that generate large responses directed at a victim); keeping this LAN-only avoids that entirely.
- Default credentials: there isn't a known default -- Pi-hole either uses the password you set, or generates a random one if you didn't (Step 2). Either way, don't leave the container logs containing that generated password sitting somewhere untrusted.
- Secrets: the admin password lives in the compose file's
environment:block in this guide, which is the simplest option for a solo homelab -- for anything more sensitive, use a Docker secret or an env file excluded from version control instead of a plain compose value. - Update strategy: Pi-hole publishes updates and advisories via its GitHub releases and blog;
docker pull pihole/pihole:latest && docker compose up -dpicks up a new image (see the Docker Compose homelab stack guide for automating this with Watchtower). - The failure that hurts most: a single Pi-hole instance going down and taking your entire network's DNS resolution with it, since you pointed every device at it with no fallback. The step that prevents it: don't point your router's secondary DNS at Pi-hole too (that just moves the single point of failure) -- either add a second Pi-hole instance for real redundancy (Go further), or accept the trade-off consciously for a first build.
Back it up and maintain it
What matters: your blocklist selection, whitelist/blacklist entries, and settings, all under /etc/pihole (mapped to ./etc-pihole in this guide's compose file) -- back up that directory. The query log is disposable (it's diagnostic history, not configuration) and doesn't need protecting the same way.
Update cadence: Pi-hole's blocklists (gravity) update automatically on its own schedule; the Pi-hole software itself, update monthly or when a security advisory lands.
What to monitor: the Query Log occasionally, for anything that looks like it's being blocked that shouldn't be (a false positive breaking a real site) -- see Troubleshooting.
Troubleshooting
Logs: docker logs pihole for the container itself; the admin UI's Tools → Query Log for DNS-specific behavior.
| Symptom | Likely cause | Diagnostic | Fix |
|---|---|---|---|
| Devices can't resolve anything after switching DNS | Pi-hole's container isn't actually running, or something else already holds port 53 on the host | docker ps to confirm the container is Up; sudo lsof -i :53 if it failed to start |
A systemd-resolved stub listener (common on Ubuntu) is the usual port-53 conflict -- disable its DNS stub listener, or run Pi-hole on a different host |
| A legitimate site is broken, not just an ad | An overly broad blocklist caught a CDN domain a real site depends on | Query Log, find the failed domain | Whitelist that specific domain from the Query Log directly -- don't remove a whole blocklist to fix one false positive |
| Query Log shows queries, but nothing is ever blocked | The device isn't actually using Pi-hole as its DNS server -- a cached previous setting, or a device with hardcoded DNS (e.g. 8.8.8.8 set manually in its Wi-Fi settings) bypasses Pi-hole entirely regardless of what the router says |
Check the device's own network settings for a hardcoded DNS entry | Remove the hardcoded entry so it picks up the router-assigned (Pi-hole) DNS |
| Gravity (blocklist) update fails | Pi-hole can't reach the blocklist source URLs -- often a temporary upstream outage, sometimes the container's own DNS resolution to fetch them | Check docker logs pihole around the failure time for the specific URL that failed |
Usually resolves itself on the next scheduled update; if persistent, confirm the container itself has working internet access |
| You also enabled Pi-hole's built-in DHCP server, and now some devices get two different DHCP-assigned IPs / lose connectivity | Two DHCP servers (your router's and Pi-hole's) are both answering DHCP requests on the same network -- Docker's default bridge networking can't even relay DHCP broadcasts correctly, which compounds this | Confirm whether you meant to enable Pi-hole's DHCP at all -- if not, disable it (Settings → DHCP) and let your router keep handling DHCP, which is what this guide assumes throughout | |
| IPv6 devices bypass Pi-hole's filtering | Pi-hole is only handling IPv4 DNS, but the device also has an IPv6 address and its own IPv6 DNS resolution path | Check whether your router advertises IPv6 DNS servers separately (Router Advertisement/DHCPv6) | Either disable IPv6 RA-provided DNS on your router (simplest), or configure Pi-hole to also serve as the IPv6 DNS resolver -- more involved, out of scope here |
Undo
Stop and remove the container (docker compose down in the Pi-hole directory), then revert your router's DNS setting back to what you noted in Before you start (or "Automatic"). Devices pick up the change on their next DHCP renewal, or immediately if you reboot them.
Go further
- Managing a Homelab with Docker Compose, Portainer, and Watchtower -- for automating Pi-hole's own updates
- Add a second Pi-hole instance and set it as your router's secondary DNS server for real redundancy -- Pi-hole's own
gravity-syncor Nebula Sync projects keep two instances' blocklists in sync automatically - Add more curated blocklists from firebog.net once the basics work and you understand how to identify and fix a false positive
- If you want Pi-hole handling DHCP too (not just DNS), read the requirements carefully first -- it needs host networking or a macvlan setup, not Docker's default bridge, and conflicts with your router's own DHCP server unless you disable one of them
Resources
Official documentation:
- Pi-hole Docker documentation -- the compose setup this guide is based on
- Pi-hole DHCP in Docker -- read before enabling Pi-hole's own DHCP server
- Pi-hole main documentation
Source and releases:
- pi-hole/pi-hole releases
- pi-hole/docker-pi-hole -- the official Docker image source
Community:
- Pi-hole Discourse forum -- the project's own official forum
- r/pihole
Go deeper:
- firebog.net -- a curated, actively-maintained list of additional blocklists, categorized by how aggressive they are
- gravity-sync -- keeps multiple Pi-hole instances' blocklists and settings in sync, for the redundancy setup mentioned in Go further
Related DaemonPress projects:
Last verified: 2026-09-21, checked against official Pi-hole documentation (see Resources above).
Changelog:
- 2026-09-21 -- Full rewrite: added a DNS-request-flow diagram, a "don't build this if" callout, Secure it (including the DNS-amplification-attack risk of exposing port 53), Back it up and maintain it, Undo, and a full Resources section. Expanded Troubleshooting from 3 to 6 entries, including two new ones specific to Pi-hole's own DHCP server (a real conflict risk) and IPv6 bypass. Corrected the Pi-hole version to the specific 6.4.3 release. Dropped the
raspberry-pitag -- the walkthrough is generic Docker with no Pi-specific steps, so the tag wasn't earned; Raspberry Pi remains a valid hardware option, just not a taught one. - 2026-09-21 (earlier) -- Original version published.