Why Your Router’s Ads Are Winning (And How to Fight Back)
Here’s something nobody tells you when you move into a new apartment or set up your home network: your ISP and every device on your LAN are phoning home constantly. Smart TVs serve ads. Phones ping tracking servers. Even your router’s admin page is probably riddled with telemetry. The internet at the DNS level is noisy, bloated, and leaky.
Enter Pi-hole: a network-wide sinkhole that sits between your devices and the internet’s ad-serving infrastructure. It’s not a browser extension. It’s not a one-device fix. It’s a DNS server that answers “I’ve never heard of that domain” to every tracker, ad server, and analytics endpoint on the blocklist — and it does it for every device on your network without installing a single client.
In this guide, we’ll deploy Pi-hole using Docker Compose so it’s easy to update, easy to back up, and easy to reason about. By the end, you’ll have a lean, mean DNS server protecting your entire network.
Prerequisites
- A Linux machine (Ubuntu/Debian/Raspberry Pi OS) with Docker and Docker Compose installed
- Port 53 (DNS) and port 80 (or a custom web UI port) available on the host
- Router access to change DNS settings (we’ll do this at the end)
- Basic comfort with the terminal
Note on port 53: On some systems, systemd-resolved already listens on port 53 for local DNS caching. If you hit a “port already in use” error later, check the troubleshooting section at the bottom.
Step 1: Create Your Project Directory
Let’s keep things tidy. Create a dedicated directory for Pi-hole:
mkdir -p ~/docker/pihole && cd ~/docker/pihole
Everything lives here. When you need to back up or migrate, this single folder has it all.
Step 2: Write the Docker Compose File
Create your docker-compose.yml:
nano docker-compose.yml
Paste the following configuration:
version: "3.8"
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80/tcp"
environment:
TZ: "America/New_York"
WEBPASSWORD: "changeme-strong-password-here"
DNSMASQ_LISTENING: "all"
FTLCONF_LOCAL_IPV4: "YOUR.HOST.IP.ADDRESS"
volumes:
- ./etc-pihole:/etc/pihole
- ./etc-dnsmasq.d:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
networks:
- pihole_net
networks:
pihole_net:
driver: bridge
Now let’s break down what each part does, because understanding this file makes debugging so much easier:
| Setting | What It Does |
|---|---|
restart: unless-stopped |
Auto-restarts on reboot and after crashes. You intentionally stopping the container is the only thing that keeps it down. |
53:53/tcp & 53:53/udp |
Exposes DNS. Both TCP and UDP are needed — standard DNS queries use UDP, but large responses and zone transfers use TCP. |
8080:80 |
Maps the Pi-hole admin web UI to port 8080 on the host. We skip 80 to avoid conflicts with existing web servers. |
WEBPASSWORD |
The password for the admin dashboard. Change it significantly. I’m not kidding. |
FTLCONF_LOCAL_IPV4 |
Your host machine’s LAN IP. This is critical for the “Client details” view to work correctly. |
volumes |
Persists Pi-hole settings and blocklists across container updates. Without this, you’d lose every config change on upgrade. |
NET_ADMIN |
Grants network administration capability. Pi-hole needs this for its internal DNS/firewall rules. |
Important: Replace YOUR.HOST.IP.ADDRESS with your actual server IP (e.g., 192.168.1.50). And please, for the love of everything, replace the WEBPASSWORD. Run openssl rand -base64 32 to generate something strong.
Step 3: Launch Pi-hole
Pull the image and start the container:
docker compose up -d
Give it about 30 seconds to initialize. Then verify it’s running:
docker compose logs --tail=20 pihole
You should see something like:
pihole | [✓] DNS service is running
pihole | [✓] Pi-hole blocking is enabled
Step 4: Test Your DNS Sinkhole
Before reconfiguring anything, let’s verify Pi-hole actually works. From any other device on your network:
dig doubleclick.net @YOUR.HOST.IP.ADDRESS
You should see the response resolve to 0.0.0.0 — that’s Pi-hole saying “that domain doesn’t exist.” The ad/tracker domain has been swallowed into the void.
For comparison, try a normal domain:
dig example.com @YOUR.HOST.IP.ADDRESS
This should return the real IP address, because Pi-hole forwards legitimate queries to the upstream DNS servers (by default, Google’s 8.8.8.8 and Cloudflare’s 1.1.1.1).
Step 5: Explore the Admin Dashboard
Open your browser and navigate to:
http://YOUR.HOST.IP.ADDRESS:8080/admin
Log in with the WEBPASSWORD you set. The dashboard is gorgeous and packed with useful info:
- Query Log: Watch DNS requests in real time. It’s hypnotic and slightly horrifying to see how often your devices phone home.
- Top Domains: See which domains are queried most — and which are being blocked.
- Blocklists: Pi-hole comes with several pre-configured blocklists. You can add more from the Group Management → Adlists section.
Take a few minutes to poke around. The dashboard is where you’ll spend 99% of your time with Pi-hole after initial setup.
Step 6: Point Your Router at Pi-hole
This is the moment it all comes together. Instead of configuring DNS device-by-device, you change your router’s DNS setting so every device on your network automatically uses Pi-hole:
- Log into your router’s admin page (usually
192.168.1.1or192.168.0.1). - Find the DHCP or DNS settings section.
- Set the primary DNS to your Pi-hole server’s IP address.
- Optionally set secondary DNS to a public resolver (like
1.1.1.1) as a fallback in case Pi-hole goes down. - Save and reboot your router (or lease-renew your devices with
sudo dhclient -r && sudo dhclienton Linux).
Now every phone, laptop, smart TV, and IoT gadget on your network is blocking ads. No per-device configuration needed.
Pro tip: If you set a secondary public DNS as a fallback, devices can bypass Pi-hole when your server is offline. For strict blocking, leave the secondary blank — but be aware that if Pi-hole goes down, devices won’t resolve anything until it comes back.
Optional: Set Up Conditional Forwarding
If you want Pi-hole to show device hostnames instead of IP addresses in the dashboard (so you can see “living-room-tv” instead of “192.168.1.47”), enable conditional forwarding:
- Go to Settings → DNS in the Pi-hole admin.
- Enable Use Conditional Forwarding.
- Enter your router’s IP as the upstream DNS.
- Enter your local domain (often
localorlan— check your router).
This lets Pi-hole ask your router for local hostnames while still blocking ads upstream.
Common Pitfalls & Troubleshooting
Port 53 Already in Use
If you see bind: address already in use in the logs, something on your host is already claiming port 53. On Ubuntu:
sudo lsof -i :53
If it’s systemd-resolved, disable its DNS stub listener:
sudo nano /etc/systemd/resolved.conf
Set:
[Resolve]
DNSStubListener=no
Then:
sudo systemctl restart systemd-resolved
sudo systemctl restart docker
Permission Denied on /etc/pihole
Pi-hole runs as a specific user inside the container and expects certain filesystem permissions. If you see permission errors:
sudo chown -R 999:999 ~/docker/pihole/etc-pihole
Or simply remove the volumes and let Pi-hole recreate them:
docker compose down
rm -rf ~/docker/pihole/etc-pihole ~/docker/pihole/etc-dnsmasq.d
docker compose up -d
No Blocking, But DNS Works Fine
Check that gravity (the blocklist updater) ran successfully:
docker exec -it pihole pihole -g
This forces a gravity update. Check the admin dashboard — the “domains on blocklist” count should be well into the hundreds of thousands.
The Web UI Is Accessible But Shows No Data
Make sure FTLCONF_LOCAL_IPV4 is set to your server’s LAN IP, not 127.0.0.1. Pi-hole uses this to correlate queries to client IPs.
Keeping Pi-hole Updated
One of the beautiful things about the Docker setup: updating is trivial.
cd ~/docker/pihole
docker compose pull
docker compose up -d
Your configuration and blocklists live in the mounted volumes, so they survive container recreation. Run this every couple of weeks, or set up a Watchtower container for automatic updates.
What to Expect After Going Live
The first 24 hours are eye-opening. You’ll watch the query log and realize that your “smart” devices are anything but private. Here’s what typically blocks on a home network:
- Smart TV telemetry (Roku, Samsung, Vizio all phone home constantly)
- Windows telemetry endpoints
- Mobile app analytics and ad networks
- IoT device health checks (some of which double as tracking beacons)
You can expect to block anywhere from 20% to 50% of total DNS queries, depending on the noisiest devices in your house. On my network, a Roku Ultra alone accounts for a staggering amount of background chatter.
The first time you open a streaming app and there are no pre-roll ads, you’ll feel like you’ve hacked the matrix. You haven’t. You just stopped asking the internet’s loudest servers the questions they shouldn’t be answering.
Welcome to a quieter internet. Your future self will thank you.
Got questions or want to share your Pi-hole stats? Drop a comment below or reach out on Discord. I’m always curious what the noisiest device in your network turns out to be.
Leave a Reply