← Self-Hosting · Security & Privacy · Replaces: per-device ad blockers, your ISP's DNS
What You'll Build
Every device looks up names like ads.example.net before loading anything. Pi-hole answers
those lookups for your whole network and simply refuses the ones on ad and tracker blocklists. Result: fewer ads and
trackers on phones, smart TVs, and consoles, with nothing installed on them.
Ports: 53 (DNS), 8081 (web admin) · Time: 30 minutes · Difficulty: Easy
Pi-hole and AdGuard Home do the same job and both need port 53. Install one, not both. Pi-hole has the bigger community; AdGuard Home has built-in encrypted DNS and wildcard rewrites.
What You Need
- Your home server from Foundations 1 (Docker installed,
~/stacksfolder). - Nginx Proxy Manager with your
*.home.example.comcertificate (recommended). - Access to your router's settings (to change the DNS server it hands out).
Step 1: Free Up Port 53 (Ubuntu)
Ubuntu runs its own small DNS helper on port 53. Turn that listener off so Pi-hole can use the port:
# Check what's on port 53
sudo ss -lntup | grep ':53 '
# Turn off systemd-resolved's stub listener (keeps a backup of the file as .orig)
sudo sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
# Point the server's own lookups at the real resolver file
sudo sh -c 'rm /etc/resolv.conf && ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf'
sudo systemctl restart systemd-resolved
Step 2: Start Pi-hole
mkdir -p ~/stacks/pihole && cd ~/stacks/pihole
nano compose.yaml
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp" # DNS
- "53:53/udp" # DNS
- "8081:80/tcp" # web admin (80 is taken by Nginx Proxy Manager)
environment:
TZ: "America/New_York"
FTLCONF_webserver_api_password: "choose-a-strong-password"
FTLCONF_dns_listeningMode: "all" # answer devices on the whole network
volumes:
- ./etc-pihole:/etc/pihole # settings, lists, query history
cap_add:
- SYS_NICE # lets Pi-hole give itself priority
docker compose up -d
sudo ufw allow 53 # allow DNS from your network
# Test: this should return an IP address…
dig @192.168.1.50 example.com +short
# …and this known ad domain should return 0.0.0.0
dig @192.168.1.50 doubleclick.net +short
(No dig? sudo apt install dnsutils.) Open http://192.168.1.50:8081/admin and log in.
Step 3: Make Every Device Use It
- Log in to your router → LAN / DHCP settings → set DNS server to
192.168.1.50. Remove any second DNS server (devices would use it to skip Pi-hole). - Reconnect a device to Wi-Fi (or reboot it) so it picks up the change.
- Browse for a minute, then check the Pi-hole dashboard: queries and a "blocked" percentage should be climbing.
If the server goes down, name lookups on your network stop and "the internet is broken". Keep Pi-hole's host reliable, or add a second Pi-hole on a Raspberry Pi and hand out both addresses.
Step 4: Blocklists and Allowlisting
- Lists: Pi-hole starts with one well-known list. Add more under Lists (paste a list URL → Add) and then run Tools → Update Gravity. Start small — huge lists break more websites.
- Something broke? Open Query Log, find the blocked domain from that device, and click Allow.
- Temporarily disable blocking from the sidebar (e.g. for 5 minutes) to test whether Pi-hole is the cause.
Step 5: Local Names for Your Apps
Make *.home.example.com resolve to your server even if your router blocks it (see the reverse proxy note):
- Settings → All settings (switch to Expert mode) → Miscellaneous →
misc.dnsmasq_lines. - Add this line → Save & Apply:
# Every name ending in home.example.com → the home server
address=/home.example.com/192.168.1.50
Add It to Your Proxy, Dashboard, and Backups
- Nginx Proxy Manager: new proxy host
pihole.home.example.com→ schemehttp,192.168.1.50, port8081, Websockets on, your wildcard certificate, Force SSL. Then openhttps://pihole.home.example.com/admin. - Homepage: add a tile in
services.yaml(how). - Uptime Kuma: add an HTTP monitor for the new address (how).
- Backups:
~/stacks/piholeis already covered by the restic script.
Troubleshooting
- "address already in use" for port 53 — Step 1 didn't take effect. Re-check
sudo ss -lntup | grep ':53 '. - Dashboard shows only one client — your router is forwarding everything itself. Setting Pi-hole as the router's upstream DNS causes this; set it as the DHCP-handed-out DNS instead.
- Ads still show on YouTube — expected. YouTube serves ads from the same domains as videos; DNS blocking can't separate them.
- Phone ignores Pi-hole — turn off "Private DNS" (Android) or iCloud Private Relay, which bypass local DNS.
What's Next?
Use Pi-hole on your phone away from home by setting it as your Tailscale DNS server — see Remote Access.
Home Assistant: a smart home without the cloud →