AdGuard Home — Where It Sits in a Layered DNS Stack
AdGuard Home is a DNS filtering proxy. That's the complete description of what it does and what it should be responsible for. The decisions that matter aren't about AdGuard itself — they're about where it sits in your network, what it hands off to, and what happens when it isn't available. Get those wrong and you've built a DNS stack that's either fragile, redundant in the wrong places, or quietly doing less than you think it is.
This is the design thinking behind a specific deployment: AdGuard running in Docker bridge mode on a Proxmox host, with OPNsense running Unbound as the upstream resolver. The Unbound configuration is covered separately. What's documented here is every meaningful decision about AdGuard's placement and role — and how those decisions would shift in a different operating context.
Host Networking vs. Bridge Mode
Most AdGuard documentation leads with network_mode: host. The reasoning is straightforward: host networking gives AdGuard direct access to all the host's interfaces, which makes port 53 binding simple and ensures every DNS query on the network reaches AdGuard without Docker's internal resolver interfering. It's the path of least resistance if your goal is getting AdGuard answering DNS as fast as possible.
The cost is everything Docker gives you for free in bridge mode. Host networking removes the container from Docker's virtual network — there are no port mappings to manage, and the container shares the host's network namespace entirely. If your homelab is just AdGuard and a few other containers that don't need to talk to each other, that cost is negligible. If you're running a reverse proxy in front of your services, host networking forces you to work around the integration rather than with it.
Bridge mode with explicit port mappings handles port 53 cleanly on a system where nothing else is competing for it. On a minimal Rocky Linux install, which is the right default for a server that isn't a desktop, systemd-resolved isn't present and port 53 is free. The conflict that makes host networking feel necessary is a full-install problem. Don't solve a problem you don't have.
In an enterprise context, if AdGuard is running on dedicated infrastructure without a reverse proxy in the picture, host networking is a reasonable choice. For most homelab setups where other containers are already running in bridge mode, staying consistent is the cleaner fit.
Where Filtering Lives vs. Where Resolution Lives
AdGuard can do both filtering and recursive resolution. It can also do just filtering and hand resolution upstream. These aren't the same deployment, and conflating them creates a stack that's harder to reason about when something breaks.
Running AdGuard as both your filter and your recursive resolver means it's doing two jobs with different failure modes. Recursive resolution has its own latency profile, its own caching behaviour, and its own edge cases around DNSSEC and negative caching. If resolution breaks, you can't easily tell whether the problem is in the filtering layer or the resolution layer — they're the same process.
Separating them — AdGuard for filtering, Unbound for resolution — keeps the responsibilities clean. Unbound handles local hostname overrides, recursive resolution, and the authoritative answer for your internal domain. AdGuard handles blocklists, query logging, and client-level filtering policy. When something breaks, the layers are debuggable independently. When you want to change your upstream resolution policy, you do it in one place without touching the filtering configuration.
The tradeoff is operational overhead. Two services instead of one, two configurations to maintain, two things that can drift. In a homelab where you're the only person maintaining the stack, that overhead is real. Whether it's worth it depends on how much you care about separation of concerns vs. simplicity of operation. For a production-oriented homelab with more than a handful of services, the separation pays for itself quickly — it did here.
The Secondary DNS Decision
Handing out a secondary DNS server via DHCP is standard practice and the reasoning is obvious: if the primary is unavailable, clients fall back to the secondary and continue resolving. What's less obvious is what the secondary should be.
The common default is a public resolver — 8.8.8.8, 1.1.1.1, whatever the router defaults to. That works for connectivity, but it bypasses your entire local DNS stack. Clients in fallback mode lose local hostname resolution, they lose any filtering policy AdGuard enforces, and depending on your setup they may lose access to internal services entirely if those hostnames only resolve through your local resolver.
A better secondary is the upstream resolver itself — in this case, the router IP where Unbound is running. Clients in fallback mode lose AdGuard's filtering layer, but they keep local hostname resolution and internal service access. That's a deliberately degraded state rather than a broken one. The services are still reachable. The filtering just isn't in place until the primary comes back up.
This is a conscious tradeoff, not an oversight. In a homelab where the primary DNS server is also running your other services, "primary server is down" and "most services are unreachable anyway" tend to correlate. The secondary DNS fallback exists to protect against temporary blips, not extended outages. Keeping it local makes that protection more useful.
In an enterprise context where the DNS server and the application servers have different availability profiles, the secondary DNS decision gets more complicated — you might want multiple AdGuard instances, or a different fallback architecture entirely. In a single-server homelab, a local secondary is the right call.
Where DHCP Lives
AdGuard has a built-in DHCP server. It's convenient for simple setups — one service handling both DNS and DHCP, with tight integration between the two. It's also a single point of failure for both, and it means AdGuard needs to be reachable before anything on the network can get an IP address.
DHCP lives on OPNsense here, not on AdGuard. OPNsense is the network boundary — it's the thing that's always on before anything else. Putting DHCP there means the network functions independently of whether the Docker host is up. Clients get IPs, they get DNS server assignments, they get their secondary fallback. AdGuard's availability becomes a filtering question, not a connectivity question.
The integration between DHCP and DNS that AdGuard's built-in server provides — automatic hostname registration for DHCP leases, for instance — is real, but it's solvable other ways. Unbound's host override configuration handles static hostname assignments cleanly. For a network where most internal hostnames are either static or don't need to resolve by DHCP-assigned name, the DHCP integration is a nice-to-have, not a hard requirement.
If your homelab is a router and a single server and you want the simplest possible setup, AdGuard's built-in DHCP is a reasonable choice. If you have a dedicated network appliance that's independent of your compute infrastructure, DHCP belongs on the appliance.
What This Stack Is Actually Doing
The layering here is deliberate. OPNsense is the network boundary — routing, firewall, DHCP, and through Unbound, the authoritative resolver for the local domain. AdGuard sits in front of Unbound for client traffic, adding filtering and query logging without touching the resolution logic. Each layer has one job.
Each layer has one job. When something breaks, the fault is usually locatable to one layer without touching the others. When something needs to change, it changes in one place. That's the design goal — not minimising the number of services, but making the stack legible under pressure.
Configuration details and versions described here reflect the deployment as of early 2026. Specific behaviours may shift with AdGuard, OPNsense, or Unbound updates — verify against current documentation before acting on specifics.