Pi-hole does one thing well, which spawned a cottage industry of sidecars—dnscrypt-proxy wrappers, cloudflared containers, reverse-proxy gymnastics—just to add encrypted upstream DNS. If you’re maintaining that collection (say, a dnsdist DoH proxy) or want modern Adblock-style rule syntax, AdGuard Home is the obvious next step: it moves those extra daemons into a single binary, giving you fewer moving parts and a web UI that does more without a command line.

Migration isn’t zero-effort, but it’s predictable. The heavy lifting is converting your whitelist and blacklist into AdGuard Home’s filter syntax and deciding how far you want to go with encrypted transport. The rest is deliberate copying of configuration from one dashboard to another.

Architecture comparison: What actually changes

The shift is not just a skin change. Under the hood, the two projects take different architectural bets:

Component Pi-hole AdGuard Home Notes
DNS engine Forked dnsmasq (FTL) Custom DNS proxy (Go) AdGuard’s proxy handles UDP, TCP, DoT, DoH, and DNSCrypt in one listener.
Web interface lighttpd + PHP Built-in HTTP server (Go) No external web server; AdGuard Home serves its own UI on a single port.
Upstream encryption Requires external proxy (cloudflared, dnscrypt-proxy) Native DoH, DoT, DNSCrypt client Fill in one URL and encrypted upstream works.
Filtering rules hosts-file + dnsmasq regex Adblock-style rules + RE2 regex AdGuard Home’s syntax supports per-client rules, $important, and response modifiers.
Client tracking MAC-to-IP via dnsmasq, conditional forwarding IP, MAC, ClientID, or hostname from DHCP AdGuard Home identifies clients natively without conditional forwarding.
DHCP server dnsmasq-based Standalone embedded server AdGuard Home’s DHCP maps hostnames directly to client identifiers.
DoH/DoT server for clients Not built-in (requires reverse proxy) Built-in: enable in one toggle AdGuard Home can simultaneously encrypt both directions.

The biggest practical shift: you’re replacing four or five processes with one. That simplifies startup ordering, reduces port conflicts, and makes log correlation easier. The trade-off is less component-level modularity—if you need a DNS feature AdGuard Home doesn’t expose, you can’t just swap in a different forwarder.

Pre-migration preparation: Back up and inventory

Before you touch anything, export everything Pi-hole knows. Start with Teleporter (Settings > Teleporter > Backup). That tarball includes adlists, whitelist, blacklist, regex, and DHCP static leases. Save it somewhere safe.

Next, grab the raw files directly—Teleporter has been known to omit custom configurations in edge cases:

sudo tar czf pihole-backup-$(date +%Y%m%d).tgz /etc/pihole/ /etc/dnsmasq.d/

If Pi-hole is your DHCP server, dump the static leases:

cat /etc/dnsmasq.d/04-pihole-static-dhcp.conf

Note your upstream DNS servers (/etc/pihole/setupVars.conf, key PIHOLE_DNS_1 and PIHOLE_DNS_2) and any conditional forwarding entries. Take a screenshot of the Pi-hole admin dashboard showing total queries, blocked percentage, and top clients—you’ll want something to compare after migration.

Finally, if you’re running on a Raspberry Pi or other single-board computer, make a full SD card image. A 10-minute rollback is infinitely better than an evening of reconfiguring.

Installation on the same hardware: Docker or bare-metal

Stop Pi-hole first so there’s no port fight:

sudo pihole disable
# or, for Docker: docker stop pihole

For a bare-metal install, the official script does the heavy lifting:

curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh

It creates a systemd service and starts a setup wizard on http://<your-ip>:3000. Walk through the wizard, but change the listen interfaces to 0.0.0.0 on port 80 for the admin UI and port 53 for DNS. The wizard will also let you set an admin password.

If you prefer Docker (cleaner rollback), the one-liner is:

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

After the container starts, complete the wizard by visiting port 3000. Once the initial setup is done, the admin UI will be on port 80 and DNS on port 53. The container exposes port 443 for future DoH/DoT client serving; you can leave it closed for now.

Stop and disable Pi-hole’s systemd service or container once migration is verified. For now, leave Pi-hole’s files in place—you’ll reference them to rebuild blocklists.

Rebuilding your blocklists in AdGuard Home

AdGuard Home uses an Adblock-compatible filtering engine, not a hosts-file parser. That means whitelist and blacklist entries need translation. The good news: blocklist subscriptions (the big domain lists) are plug-and-play. Go to Filters > DNS blocklists, and add the same URLs you had in Pi-hole’s adlists.list. AdGuard Home even offers a curated selection you can check with one click.

Custom block rules live under Filters > Custom filtering rules. Here’s the mapping:

  • Pi-hole exact blacklist entry doubleclick.net → AdGuard Home rule ||doubleclick.net^
  • Pi-hole whitelist entry s.youtube.com → @@||s.youtube.com^
  • Pi-hole regex \.?ad.*\.doubleclick\.net$ → same regex, but note AdGuard Home uses RE2 syntax. Test each one with the built-in regex tester. RE2 doesn’t support backreferences, so any Pi-hole regex that relies on \1 will fail silently.

For large custom lists, the ||domain^ syntax is almost always what you want. It covers the exact domain and all subdomains. If you need to block a domain only on a specific client, prefix the rule with the client identifier:

||ads.example.com^$client=192.168.1.50

I’d recommend rebuilding the custom rules from scratch instead of bulk-importing. Pi-hole’s default blocklist management encourages accumulation of cruft; migration is an excuse to prune.

Configuring upstream DNS-over-HTTPS

This is where AdGuard Home earns its keep. Open Settings > DNS settings and replace the default upstream DNS entries with DoH URLs. For Quad9:

https://dns.quad9.net/dns-query

Or a mix of DoH and plain UDP for resilience:

https://dns.quad9.net/dns-query
9.9.9.9

AdGuard Home will resolve the hostname in the DoH URL using bootstrap DNS servers (set just below the upstream field). The defaults (9.9.9.10, 149.112.112.10, 2620:fe::10) are Quad9-owned and adequate, though you can substitute your ISP’s DNS if you prefer.

To verify DoH is active, open the Query Log, trigger a few lookups from a client, and look at the “Upstream” column. You’ll see entries like “https://dns.quad9.net/dns-query (encrypted)” if the connection succeeded. If the column shows a plain IP address, check bootstrap DNS—AdGuard Home couldn’t resolve the DoH hostname.

For the truly paranoid, a tcpdump confirms encryption:

sudo tcpdump -i any port 443 and host dns.quad9.net

You’ll see TLS handshakes, not plaintext DNS.

Migrating DHCP, clients, and static leases

If Pi-hole ran DHCP, AdGuard Home should take it over cleanly. The logic: AdGuard Home’s DHCP server learns client hostnames from DHCP requests and maps them to client identifiers automatically. That eliminates the need for Pi-hole’s conditional forwarding trick altogether.

First, disable Pi-hole’s DHCP server (Settings > DHCP) and jot down the IP range, router (gateway) IP, and domain name. Then enable AdGuard Home’s DHCP under Settings > DHCP settings: set the same range and gateway. For static leases, you can either enter them manually in the web UI or edit the AdGuard Home configuration file directly (/opt/adguardhome/conf/AdGuardHome.yaml under dhcp.static.leases). The static lease format is:

dhcp:
  static:
    leases:
      - mac: "AA:BB:CC:DD:EE:FF"
        ip: "192.168.1.50"
        hostname: "printer"

Clients will renew their leases automatically once Pi-hole’s DHCP stops responding. To force it, restart client network interfaces or shorten Pi-hole’s lease time before the switch.

If you keep your router as the DHCP server, client identification becomes manual. AdGuard Home will see IP addresses only, and you’ll need to assign names under Settings > Client settings. Add a client by IP and give it a tag; filtering rules can then be scoped with $client=tag-name. It works, but the hostname automation is lost.

Testing and validation: Did it work?

Prove it works, don’t just assume. Start with a blocked domain:

dig @192.168.1.2 doubleclick.net
# Should return 0.0.0.0 or NXDOMAIN depending on your blocking mode

Then confirm an allowed domain resolves normally:

dig @192.168.1.2 google.com

Next, verify DoH upstream resolution in the query log. Filter by client, trigger a fresh lookup for a domain you haven’t visited recently, and confirm the upstream is marked as encrypted. You can also use AdGuard Home’s built-in “Check filtering” tool (Filters > Query Log > Test filtering) to see which rule, if any, blocked a domain.

Client identification is the third checkpoint. Browse to a few sites from a known device, and look at the query log’s client column. If you see only IPs, your DHCP hostname hand-off probably failed. For AdGuard Home’s own DHCP, client hostnames should appear within minutes. If not, verify the client actually sent a hostname in its DHCP request (Linux: dhclient -v, Windows: ipconfig /renew) and that AdGuard Home’s DHCP range matches.

Finally, performance: AdGuard Home’s dashboard shows average processing time. A local cache hit should be under 1 ms; a DoH upstream query will add whatever the internet latency is. If processing times are an order of magnitude higher than Pi-hole’s, check if you’re using the RE2 regex engine heavily—poorly written regex can eat CPU.

When Pi-hole is still the right answer

Migration isn’t a one-way street with no downsides. Pi-hole remains the better choice when:

  • You already have a stable dnscrypt-proxy or cloudflared setup and no interest in re-doing it.
  • You rely heavily on conditional forwarding (e.g., for an Active Directory domain). AdGuard Home currently lacks per-domain upstream forwarding.
  • Your hardware is extremely constrained. Pi-hole’s FTL engine on a Pi Zero W with a couple million domains is a known quantity; AdGuard Home’s Go binary uses more RAM for the same list size.
  • You prefer dnsmasq’s extensibility—adding custom config fragments to /etc/dnsmasq.d/ is trivial, while AdGuard Home’s configuration is a YAML monolith.
  • Regex backreferences matter to your ruleset. RE2’s lack of backreferences can be a dealbreaker.

If none of those apply, the move is a net reduction in complexity.

Conclusion

A Pi-hole to AdGuard Home migration is a Saturday morning project. The sequence is methodical: back up Pi-hole, install AdGuard Home, translate the rule set, and shift DHCP. The payoff is a single binary that gives you encrypted upstream, Adblock-grade filtering, and client-aware query logging without add-ons. Verify encryption with the query log, validate blocking with a test domain, and keep your Pi-hole Teleporter backup on hand for a quick rollback. If you’ve been wrestling with sidecars just to get DNS-over-HTTPS, the time spent converting rules is a fair trade for the simplicity you’ll have afterward.