Network-wide ad blocking on a Raspberry Pi: Pi-hole or AdGuard Home

Block ads for every device on your network: set up Pi-hole v6 or AdGuard Home on a Raspberry Pi with Docker, point your router's DNS at it, and test it.

By Toni LukeUpdated 6 min read
Contents8 sections

Both tools do the core job well. This guide installs either one in Docker, then covers the router step, a test, and the problems the official docs warn about.

Which one?

Pick one. They both want port 53, so they can't share a host.

  • Pi-hole (v6) suits you if you like one tool per job: Pi-hole for blocking, and optionally Unbound for recursive DNS. It has the bigger community of blocklists and guides. Current versions: core 6.4.3 (6 July 2026), FTL 6.7.1 (19 September 2026) and web 6.6 (6 July 2026).
  • AdGuard Home suits you if you want encrypted upstream DNS (DoH, DoT, DoQ) and per-client rules in one binary. Current version: 0.107.79 (18 August 2026).

The full comparison is in Pi-hole vs AdGuard Home. If you can't decide, pick either one. Switching later just means changing one IP in your router.

Prerequisites

  • A Raspberry Pi (or any Linux box) that stays on. Pi-hole's prerequisites are 512 MB RAM and 2 GB of free disk (4 GB recommended). AdGuard's docs don't state a minimum.
  • A static IP for the Pi. Pi-hole's docs require one, and a DHCP reservation on the router is fine. Your router will hand this address to every device, so it can't change.
  • Docker with Compose. On a 64-bit Pi, follow step 1 of the Vaultwarden on a Raspberry Pi guide.
  • Access to your router's admin page.

Option A: Pi-hole in Docker

Step A1: write compose.yaml

This is the official quick-start file from the docker-pi-hole README, trimmed. Changes: the commented-out DHCP, NTP and dnsmasq.d lines are removed, the timezone is a placeholder, and the password is a placeholder. The cap_add block is kept as published, with the README's notes on when each capability is needed.

yaml
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      # DNS Ports
      - "53:53/tcp"
      - "53:53/udp"
      # Default HTTP Port
      - "80:80/tcp"
      # Default HTTPs Port. FTL will generate a self-signed certificate
      - "443:443/tcp"
    environment:
      TZ: 'Your/Timezone'
      # Set a password to access the web interface. Not setting one will result in a random password being assigned
      FTLCONF_webserver_api_password: 'change-me'
      # If using Docker's default `bridge` network setting the dns listening mode should be set to 'ALL'
      FTLCONF_dns_listeningMode: 'ALL'
    volumes:
      - './etc-pihole:/etc/pihole'
    cap_add:
      - NET_ADMIN   # Required if you are using Pi-hole as your DHCP server, else not needed
      - SYS_TIME    # Required if you are using Pi-hole as your NTP client
      - SYS_NICE    # Optional, if Pi-hole should get some more processing time
    restart: unless-stopped

In v6, settings passed as FTLCONF_ environment variables become read-only in the web interface, and Pi-hole calls that the preferred approach in Docker. If you leave out the password, the configuration docs say a random one is assigned and printed to the log. docker logs pihole shows it (look for "random password").

Step A2: start it

bash
docker compose up -d

The README notes that cron inside the container refreshes your lists and flushes logs once a week, early on Sunday morning.

Step A3: open the dashboard

Browse to http://<pi-ip>/admin/ and log in with your password.

Option B: AdGuard Home

Step B1 (Docker): create folders and run the container

AdGuard's Docker page uses a single docker run. Here it is trimmed to plain DNS and the web interface. Changes: the DHCP ports (67/68), DNS-over-TLS/QUIC (853), DNSCrypt (5443), pprof (6060) and 443 mappings are removed, and the example paths are replaced. Add any of them back if you enable those features. You need 443 for HTTPS and DNS-over-HTTPS, for example.

bash
mkdir -p ~/adguard/work ~/adguard/conf
docker run \
    -d \
    --name adguardhome \
    -p 53:53/tcp -p 53:53/udp \
    -p 80:80/tcp -p 3000:3000/tcp \
    --restart unless-stopped \
    -v ~/adguard/work:/opt/adguardhome/work \
    -v ~/adguard/conf:/opt/adguardhome/conf \
    adguard/adguardhome

One Docker-specific catch from the same page: in bridge mode, AdGuard Home sees every query as coming from Docker's gateway (something like 172.17.0.1), not from your devices. To see real client IPs, the docs say to add --network host, and then the -p mappings aren't needed.

Step B1 (no Docker): the native Pi install

AdGuard's Raspberry Pi guide installs a single binary instead:

bash
cd
wget 'https://static.adguard.com/adguardhome/release/AdGuardHome_linux_armv6.tar.gz'
tar -f AdGuardHome_linux_armv6.tar.gz -x -v
cd AdGuardHome
sudo ./AdGuardHome -s install

The guide says to replace armv6 "with the ARM version that is best supported by your Pi". AdGuard publishes ARMv6, ARMv7 and 64-bit ARM builds. The last line comes from the getting-started page and registers AdGuard Home as a system service.

Step B2: run the setup wizard

Expected result, per the getting-started page: on first run, AdGuard Home listens on port 3000 and prompts you to open http://<pi-ip>:3000. Work through the wizard. It sets the admin interface's listen address and port (80 by default) and creates your login.

Step 2 (both): point your router at it

Pi-hole's post-install page puts it simply: configure your router "to have DHCP clients use Pi-hole as their DNS server." AdGuard's instructions say the same thing.

  1. Log in to your router, usually at an address such as http://192.168.0.1/ or http://192.168.1.1/.
  2. Find the DHCP or DNS settings: fields that take IP addresses as four groups of numbers.
  3. Enter the Pi's IP as the DNS server, and save.

Devices pick up the new DNS server when they renew their DHCP lease. Reconnecting to Wi-Fi is the quickest way to trigger that.

If your router won't let you change DNS, both projects document running their own DHCP server instead. Turn off the router's DHCP first. Pi-hole's Docker setup needs the NET_ADMIN capability and port 67 for that, and AdGuard's Docker docs say to use --network host. As a last resort, set the DNS server by hand on each device.

Step 3 (both): test that it's blocking

  • AdGuard Home: its Pi guide gives a one-line check to run on the Pi. If it's working, the answer ends in Host doubleclick.net not found: 3(NXDOMAIN).

    bash
    host doubleclick.net 127.0.0.1
  • Pi-hole: the Docker tips page suggests loading http://pi.hole/admin/ from a device on your network. That name only resolves through Pi-hole, so if it loads, the device is using Pi-hole. Then watch queries arrive in the dashboard's query log.

Also note, from Pi-hole's post-install page: the Pi itself doesn't use Pi-hole automatically. If you do point the host at itself, and Pi-hole fails, the host loses DNS. That can block the repair.

Troubleshooting

Only issues the official docs describe are listed here.

  • Port 53 is already in use (bind: address already in use). On systems running systemd-resolved, such as Ubuntu 17.10+ and Fedora 33+, its stub listener holds port 53. Check with sudo lsof -i :53. Pi-hole's fix is:

    bash
    sudo sh -c 'mkdir -p /etc/systemd/resolved.conf.d && printf "[Resolve]\nDNSStubListener=no\n" | tee /etc/systemd/resolved.conf.d/no-stub.conf'
    sudo sh -c 'rm -f /etc/resolv.conf && ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf'
    systemctl restart systemd-resolved

    AdGuard's FAQ gives the equivalent, with a [Resolve] file that also sets DNS=127.0.0.1. For native installs, the setup screen shows a Fix button next to the "address already in use" message, which disables the stub listener for you. The FAQ says Docker and Snap users have to make the change by hand.

  • Port 80 is taken. Pi-hole's tips page says to stop the other web service, and to stop it auto-starting. In v6, Pi-hole's docs say FTL falls back to 8080/8443 if 80/443 are busy.

  • Every query shows the same client IP (AdGuard in Docker). Use --network host, as above.

  • YouTube ads still play. That's expected. AdGuard's FAQ explains that DNS blocking can't remove ads served from the same domain as the content, and names YouTube as an example. The same limit applies to Pi-hole. A browser content blocker is the tool for that.

  • Updating. For Pi-hole in Docker, the README's point about volumes is what makes updates safe: ./etc-pihole keeps your data when the container is recreated from a newer image (docker compose pull && docker compose up -d). For AdGuard, the docs say auto-update is disabled in Docker: pull adguard/adguardhome, stop and remove the old container, and rerun the command. Native installs update from the web interface's Update now button.

What to do next

Sources (11)Show
  1. docker-pi-hole README (Compose quick start, v6 FTLCONF_ variables) · accessed 2026-09-29
  2. Pi-hole Docs: Docker configuration (web password, random password in logs) · accessed 2026-09-29
  3. Pi-hole Docs: Docker tips and tricks (systemd-resolved port 53, pi.hole/admin test) · accessed 2026-09-29
  4. Pi-hole Docs: Post-install (router DHCP DNS, host resolution warning) · accessed 2026-09-29
  5. Pi-hole Docs: Prerequisites (static IP, RAM, disk), as summarised in the Pi-hole profile · accessed 2026-09-29
  6. AdGuard Home KB: Getting started (ports, first start, service commands, configuring devices) · accessed 2026-09-29
  7. AdGuard Home KB: Raspberry Pi (install, doubleclick.net test) · accessed 2026-09-29
  8. AdGuard Home KB: Docker (quick start, port list, client IPs, resolved daemon) · accessed 2026-09-29
  9. AdGuard Home KB: FAQ (bind: address already in use, what DNS blocking can't do) · accessed 2026-09-29
  10. GitHub releases API: pi-hole/pi-hole core 6.4.3, FTL 6.7.1, web 6.6, as recorded in the Pi-hole profile · accessed 2026-09-29
  11. GitHub releases API: AdguardTeam/AdGuardHome (0.107.79, 2026-08-18), as recorded in the AdGuard Home profile · accessed 2026-09-29