AdGuard Home Setup

Why AdGuard Home?

AdGuard Home was my first "real" service after Portainer. Three main reasons:

  • Privacy: — Network-wide ad and tracker blocking at the DNS level
  • Local DNS: — No more typing IP addresses; use hostnames like adguard.tld.com and resolve locally
  • Security: — DNS-over-HTTPS with malware-blocking upstream resolvers
The eye-opening part

After one week of running AdGuard Home: 254,800 queries, of which 15.7% were blocked. That's roughly 40,000 blocked requests in 7 days — and I noticed zero downsides.

The amount of background tracking is pretty significant, even on "idle" devices.


Docker Compose Setup

The Problem: Port 53 Already in Use

When I first tried to start the container, I got this error:

Error response from daemon: failed to bind host port 0.0.0.0:53/tcp: address already in use

Running ss -tulpn | grep :53 revealed the culprit: systemd-resolved was already listening on port 53.

The solution

Instead of disabling systemd-resolved entirely, I bound AdGuard Home to my server's LAN IP only. This way:

  • systemd-resolved keeps port 53 on loopback (for the host itself)
  • AdGuard Home gets port 53 on the LAN interface (for all other devices)

Docker Compose File

services:
  adguardhome:
    image: adguard/adguardhome
    container_name: adguardhome
    restart: always
    ports:
      # Bind to LAN IP only (not 0.0.0.0) to avoid conflict with systemd-resolved
      - "192.168.178.<HOST_ID>:53:53/tcp"
      - "192.168.178.<HOST_ID>:53:53/udp"
      # IPv6 ULA address
      - "[fd00::53]:53:53/tcp"
      - "[fd00::53]:53:53/udp"
      # Web UI for initial setup (keep 80/443 free for Traefik later)
      - "3000:3000/tcp"
    volumes:
      - ./appdata/adguardhome/conf:/opt/adguardhome/conf
      - ./appdata/adguardhome/work:/opt/adguardhome/work
    logging:
      driver: json-file
      options:
        max-size: "3m"
        max-file: "3"

Replace 192.168.178.<HOST_ID> and fd00::53 with your server's actual IPs.

Why Port 3000 for the Web UI?

I kept the admin interface on port 3000 instead of 80/443 because I want to use those ports for Traefik later as a reverse proxy.


Configuration

Upstream DNS Servers

I use privacy-focused, security-enhanced upstream resolvers:

  • Primary: https://dns.quad9.net/dns-query — Quad9 with DNSSEC + malware blocking
  • Fallback: https://security.cloudflare-dns.com/dns-query — Cloudflare's security profile

Don't forget to enable DNSSEC validation in AdGuard Home settings.

Blocklists

My current blocklist setup (total rules: ~594,000):

A note on blocklists

More rules != better. Overlapping lists waste resources, and overly aggressive lists can break legitimate services. Start with AdGuard DNS filter + HaGeZi Pro, then add specific lists based on your devices.


Network Integration

The Dilemma: 100% Filtering vs. Redundancy

I had two options for integrating AdGuard Home into my network:

Option 1: Server as DNS for all clients

Set the homeserver as the DNS server in router settings (DHCP). All clients get it directly.

Pro:

  • 100% of traffic goes through AdGuard
  • Per-client statistics

Con:

  • If server is down → no DNS → no internet for entire network
Option 2: Router uses AdGuard as upstream (my choice)

Router stays as DNS for clients, but forwards queries to AdGuard Home. External DNS (e.g., Quad9) as fallback.

Pro:

  • Redundancy: network works even if server is down

Con:

  • All queries appear to come from router IP (no per-client stats)
  • Fallback DNS bypasses filtering

My Decision

I chose Option 2. Redundancy matters more to me than perfect statistics (which I don't really care about anyway). If my NUC is down for maintenance, the network shouldn't grind to a halt.

Current DNS path:
Device → Router → AdGuard Home → Quad9/Cloudflare
                ↘ (fallback) → Quad9 directly
IPv6: A Detour I Didn't Plan

Initially, I wanted to disable IPv6 entirely in my router settings. My reasoning: keep things simple, avoid the complexity of setting up static IPv6 addresses, and prevent clients from potentially bypassing AdGuard Home via IPv6 DNS servers.

However, I had to re-enable IPv6 later when I set up VPN access to my home network. Due to CGNAT (Carrier-Grade NAT), I don't have a static public IPv4 address, which meant I couldn't reliably connect from outside via IPv4. IPv6 was the solution.

This meant I had to configure AdGuard Home properly for IPv6 as well:

  • Set a static IPv6 address for the NUC (ULA: fd00::53)
  • Configure the router to use the NUC as the primary DNS resolver for both IPv4 and IPv6
  • Ensure both address families route through AdGuard Home to maintain filtering coverage

IPv6 is also the future — IPv4 address exhaustion is real, and eventually, IPv6-only networks will become the norm. Better to learn it now than avoid it.


Lessons Learned

  • Port conflicts are common: — Always check what's already using a port before binding
  • Bind to specific IPs: — Avoids conflicts and is more secure than 0.0.0.0
  • Plan for failure: — A dead DNS server = dead internet for everyone
  • Start with fewer blocklists: — Add more only if needed; too many can cause false positives
  • 15% blocked is normal: — Modern devices are incredibly chatty with telemetry

What's Next

  • [x] Set up local DNS rewrites for local hostnames
  • [ ] Integrate with Traefik for HTTPS access to the admin UI
  • [x] Document setup