← All Guides
beginner

How to Set Up Pi-hole or AdGuard Home for Network-Wide Ad Blocking

Compare Pi-hole and AdGuard Home, then set up whichever fits your homelab. Full Docker Compose configs, DNS integration, and blocklist recommendations.

Budget Homelab ·
networkingdockerhow-tobeginner

DNS-level ad blocking is one of the first things I’d recommend to anyone building a homelab. You install it once, point your network at it, and every device — phones, TVs, laptops, smart speakers, game consoles — stops phoning home to ad networks and tracking services. No browser extensions. No per-device configuration. It just stops.

Two tools dominate this space: Pi-hole and AdGuard Home. Both do the same core job. The differences are in how they’re configured, what the interface looks like, and what each one does better than the other.

If you already have Pi-hole running (or want a dedicated Pi-hole walkthrough), the standalone Pi-hole guide covers that in depth. This guide is for people who haven’t picked yet — or who want to understand the tradeoffs before committing. You’ll get full Docker Compose setups for both, and a clear recommendation on which to pick.

This guide uses Docker Compose. If you’re new to it, read the Docker Compose basics guide and the Getting Started guide first.


Pi-hole vs AdGuard Home: What’s Actually Different

Both tools work the same way at the network level: they act as a DNS resolver, check every query against blocklists, and return nothing for blocked domains. The differences are in everything around that core function.

FeaturePi-holeAdGuard Home
Web UIFunctional, dated lookModern, polished
DNS-over-HTTPSRequires Cloudflared sidecarBuilt-in, no extra container
DNS-over-TLSNoBuilt-in
DHCP serverYesYes
Regex blockingNoYes
Per-client settingsBasic (groups)Full per-client overrides
Blocklist formatHosts file formatHosts + Adblock Plus format
Community blocklistsVery large ecosystemGrowing, slightly smaller
Stats/query logExcellent, granularGood, slightly less detail
Resource usage~50MB RAM~30MB RAM
Setup complexityModerateSimpler initial config

Both are actively maintained. Both are free and open source. Neither requires an account or phoning home.


Which Should You Pick?

Pick AdGuard Home if:

Pick Pi-hole if:

My setup uses Technitium DNS as the primary resolver for local DNS and split-DNS, with filtering handled at that layer. But if you’re not running a separate DNS server, either Pi-hole or AdGuard Home works well as your single DNS stack. AdGuard Home is where I’d start a fresh install today.


Prerequisites

If you’re on Ubuntu and systemd-resolved is using port 53, disable it:

sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

Option A: Docker Compose Setup for AdGuard Home

Create the directory:

mkdir -p /opt/adguard/work
mkdir -p /opt/adguard/conf

Create /opt/adguard/docker-compose.yml:

services:
  adguardhome:
    image: adguard/adguardhome:latest
    container_name: adguardhome
    network_mode: host
    volumes:
      - /opt/adguard/work:/opt/adguardhome/work
      - /opt/adguard/conf:/opt/adguardhome/conf
    restart: unless-stopped

network_mode: host is required for the same reason as Pi-hole — DNS runs on port 53, and host networking is the cleanest way to bind to it without jumping through port-mapping hoops.

Start it:

cd /opt/adguard
docker compose up -d

The initial setup wizard runs at http://YOUR_SERVER_IP:3000. Walk through it once — it asks for your admin credentials, your DNS upstream servers, and what interfaces to listen on. After that, the admin interface moves to port 80 (or whatever you configure). I set mine to port 3000 to avoid conflicts with other containers.

To keep it on port 3000 permanently, set the web interface port during the wizard. Or edit /opt/adguard/conf/AdGuardHome.yaml after setup:

http:
  address: 0.0.0.0:3000

Then restart the container.

Configuring AdGuard Home

Upstream DNS: Settings > DNS Settings > Upstream DNS Servers. Use https://dns.cloudflare.com/dns-query for DNS-over-HTTPS. Or use 1.1.1.1 if you want plain DNS. If you’re running Technitium as your local resolver, point it here instead of the router — see the Technitium DNS guide for that configuration.

Bootstrap DNS: Required for resolving the DoH endpoint itself. Use 1.1.1.1 and 8.8.8.8.

Blocklists: Filters > DNS Blocklists > Add Blocklist. Start with:

After adding lists, click “Update Filters” and check the dashboard to confirm the count jumped.

Per-client settings: This is AdGuard Home’s standout feature. Go to Settings > Client Settings and add your devices by IP or MAC. You can then assign different filtering rules per device — full blocking for most devices, looser rules for a work laptop that needs certain ad-adjacent domains, no blocking for a device that’s constantly hitting false positives.


Option B: Docker Compose Setup for Pi-hole

Create the directory:

mkdir -p /opt/pihole

Create /opt/pihole/docker-compose.yml:

services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    network_mode: host
    environment:
      TZ: "America/New_York"
      WEBPASSWORD: "changeme"
      PIHOLE_DNS_: "1.1.1.1;1.0.0.1"
      DNSMASQ_LISTENING: "all"
    volumes:
      - /opt/pihole/etc-pihole:/etc/pihole
      - /opt/pihole/etc-dnsmasq.d:/etc/dnsmasq.d
    restart: unless-stopped
    cap_add:
      - NET_ADMIN

Change WEBPASSWORD to something real. Change TZ to your timezone. The PIHOLE_DNS_ value is where Pi-hole forwards non-blocked queries — Cloudflare here, but you can use any upstream resolver.

Start it:

cd /opt/pihole
docker compose up -d

Admin interface is at http://YOUR_SERVER_IP/admin.

Configuring Pi-hole

Blocklists: Group Management > Adlists. Add:

After adding, go to Tools > Update Gravity. Pi-hole downloads all the lists and compiles them into its database. This takes a minute or two. Watch the blocked domain count after — it typically goes from ~170K default to 400K+ after adding a few good lists.

Adding DNS-over-HTTPS to Pi-hole: Pi-hole itself doesn’t support DoH natively. If you want encrypted upstream queries, add a Cloudflared container alongside it:

services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    network_mode: host
    environment:
      TZ: "America/New_York"
      WEBPASSWORD: "changeme"
      PIHOLE_DNS_: "127.0.0.1#5053"
      DNSMASQ_LISTENING: "all"
    volumes:
      - /opt/pihole/etc-pihole:/etc/pihole
      - /opt/pihole/etc-dnsmasq.d:/etc/dnsmasq.d
    restart: unless-stopped
    cap_add:
      - NET_ADMIN

  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    network_mode: host
    command: proxy-dns --port 5053 --upstream https://dns.cloudflare.com/dns-query
    restart: unless-stopped

This runs Cloudflared as a DNS-over-HTTPS proxy on port 5053, and Pi-hole uses it as upstream. More moving parts than AdGuard Home’s built-in DoH — which is one reason AdGuard Home has the edge for simpler setups.


Pointing Your Network at Either One

The blocker is set up. Now your devices need to actually use it.

Log into your router and find DHCP settings. Change the Primary DNS to your server’s IP. Every device on the network starts using your blocker automatically.

For secondary DNS: you can leave it blank (devices fall back to your blocker only) or set a public DNS like 1.1.1.1 as a fallback. Leaving it blank means if your blocking container goes down, DNS fails entirely — good for forcing you to notice, bad if you need reliable internet. Your call.

Common router paths:

After saving, release and renew DHCP leases on your devices (or just wait — leases refresh on their own). Check nslookup ads.google.com YOUR_SERVER_IP from a device to confirm queries are routing through.

Per-device (if your router doesn’t support it)

Set the DNS server manually on each device. In network/Wi-Fi settings, switch from Automatic/DHCP DNS to Manual and enter your server IP.


Integrating with Technitium DNS

If you’re running Technitium as your primary local resolver (which lets you use internal domain names like jellyfin.homelab.lan), you have two clean options:

Option 1: Technitium forwards to Pi-hole or AdGuard for all external queries. Technitium handles local zones. Everything else goes through your blocker first, then out to the internet. Set this in Technitium under Settings > Recursion > Use custom DNS forwarders, and enter your blocker’s IP.

Option 2: Pi-hole or AdGuard uses Technitium as upstream. The blocker handles filtering. Technitium handles local resolution for anything that passes through. This works but adds a lookup hop.

Option 1 is cleaner. The Technitium DNS guide covers the forwarder configuration in detail. The short version: in Technitium’s DNS settings, set your Pi-hole or AdGuard Home IP as the forwarder. Technitium resolves local names directly; everything external goes through your blocker first.

Either way, point your router’s DHCP DNS at Technitium (not the blocker directly). Technitium becomes the single DNS entry point for your network.


Blocklist Recommendations

Don’t overthink this. Three lists handle 95% of what you want to block:

OISD Big (https://big.oisd.nl) - Comprehensive, well-maintained, low false positives. This is the one list I’d run even if I only ran one.

HaGeZi Multi Pro (https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt) - Aggressive tracking and ad blocking. More domains than OISD, slightly more false positives on edge cases.

StevenBlack Hosts (https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts) - Long-running community-maintained list. Solid baseline coverage.

Combined, you’ll typically land between 400K and 700K blocked domains depending on list versions. That sounds like a lot. It is. Most of them you’ll never notice because the domains are never queried — they’re background tracking infrastructure that your devices would otherwise hit silently.

What to avoid: lists with millions of domains. They bloat your gravity database, slow query resolution, and generate constant false positives. Bigger is not better here.


Troubleshooting Common Issues

Something stopped working after setup

Check the query log first. In Pi-hole, it’s under Query Log. In AdGuard Home, it’s under Query Log. Find the domain that was requested, look at whether it was blocked, and whitelist it if needed. Nine times out of ten this is what’s happening.

DNS resolution is slow or timing out

Your upstream resolver might be the issue, not the blocker. Test by temporarily setting a device to use 1.1.1.1 directly and seeing if it’s faster. If it is, check whether your upstream forwarder (Cloudflared or the direct upstream IP) is reachable from the container.

Port 53 conflict on startup

Run ss -tlnp | grep :53 on the host. If systemd-resolved is there, disable it using the commands in the Prerequisites section. If another container has it, find the conflict.

Blocklist isn’t updating

In Pi-hole, run Tools > Update Gravity manually and watch for errors. Common cause: the list URL is down or the format changed. Remove the bad list and add an alternative.

Container restarts on its own

Check logs: docker logs pihole or docker logs adguardhome. Memory pressure on low-RAM systems can cause this. Both tools run comfortably on 256MB of dedicated RAM — if you’re constrained, set a memory limit in the compose file and monitor.

The admin UI isn’t loading

For Pi-hole: make sure port 80 isn’t claimed by another container. For AdGuard Home: check what port you configured during setup. If you’re behind a reverse proxy (Nginx Proxy Manager), double-check the proxy host settings.


DNS-level blocking is one of those homelab wins that keeps paying off. Set it up once, add a couple of good blocklists, and forget about it. The dashboard is there when you’re curious about what your TV is actually doing at 2am.

For more tools to add to your stack once you have blocking in place, the best self-hosted apps roundup is a good next read.