Most homelabbers have two DNS servers listed in DHCP: primary ad-blocker, and the router as "backup".
A few weeks ago, I actually measured what a phone feels when that primary dies. Two uncomfortable truths:
- A client with two DNS servers does not fail over; it waits. The stub resolver queries the dead primary, waits out its full 2-second timeout, then falls back to the secondary. It pays that 2-second penalty on every single query.
- The "backup" quietly gets real traffic even during peacetime.
That sent me down a ridiculous rabbit hole: building a true zero-downtime, sub-second failover DNS fabric. I stood up 15 Incus containers on an Orange Pi 6 Plus, peered them over BGP ECMP with BFD (300 ms timers) to a MikroTik CCR2004, and ran a sequential benchmark across five leading DNS architectures:
- AdGuard Home (
10.1.9.53) - Blocky + Unbound hybrid (
10.1.9.54) - Unbound standalone (
10.1.9.55) - Technitium DNS (
10.1.9.56) - Knot Resolver 6 (
10.1.9.57)
The Benchmark Scorecard
Tested sequentially with service restarts and 10s cooldowns to eliminate RK3588 cache/core contention:
| Metric | AdGuard Home (.53) | Blocky + Unbound (.54) | Unbound Standalone (.55) | Technitium (.56) | Knot Resolver 6 (.57) |
|---|---|---|---|---|---|
| Cold Latency (p50) | 12.89 ms | 31.39 ms | 38.51 ms | 6.60 ms | 48.82 ms |
| Warm Cache (p50) | 2.04 ms | 11.06 ms (2.46M rules) | 1.17 ms | 1.44 ms | 0.99 ms |
| Throughput (dnsperf) | 4,999 QPS (1 lost) | 4,998 QPS (0 lost) | 5,000 QPS (1 lost) | 1,541 QPS (300 lost) | 5,000 QPS (1 lost) |
| Block Rate (101 trackers) | 93.1% | 96.0% | 82.2% | 94.1% | 82.2% |
| RFC 8767 Serve-Stale | PASS (3.63 ms) | PASS (2.54 ms) | PASS (1.53 ms) | FAILED (3.1s timeout) | PASS (2.46 ms) |
| BFD Failover Jitter (500 QPS) | 1 drop (0.19%) | 1 drop (0.19%) | 1 drop (0.18%) | 8 drops (4.04%) | 0 drops (0.00%) |
Four things that surprised me:
1. The day the anycast trio deleted itself
The biggest facepalm of the project. During an ISP outage, I watched all three Knot nodes withdraw their BGP routes simultaneously (paths=0), killing DNS for the entire house.
The resolver containers were 100% healthy, with warm caches packed with Wikipedia and local records. But my node health script was naively asking: "Can this container reach 1.1.1.1 on the public internet?"
When the WAN blipped, all three answered "no" at the exact same instant, and the failover mechanism nuked the service at the precise moment cached DNS was needed most.
Lesson: Anycast health probes must test local health only (e.g. querying an uncacheable .lan record against the gateway), never the public WAN.
2. The AdGuard "SafeBrowsing" Tax
If you run AdGuard Home with the "browsing security web service" enabled, put a packet capture on the wire. Inside AdGuard, cold lookups arrive at t = 0 ms, but the upstream query isn't dispatched until t = 14.8 ms.
It waits ~12–15 ms synchronously checking AdGuard's cloud API before even starting recursive resolution. In my 27,000-query household test, SafeBrowsing blocked exactly 0 domains. Unchecking it instantly reclaimed ~12 ms on every cache miss.
3. Technitium's Concurrency & Stale-Serving Wall
Technitium was astonishingly fast at cold recursion (6.6 ms p50) and has the cleanest Extended DNS Error (EDE) codes. But under high dnsperf concurrency, .NET hit a wall at ~1,500 QPS while Go and C sailed to 5,000 QPS. Worse, when upstream DNS was severed, Technitium hung for 3.1 seconds instead of serving expired records via RFC 8767.
4. Why Blocky + Unbound won my permanent setup
I expected Knot 6 or native Unbound to win on raw latency. But homelab DNS isn't chosen on an Excel sheet; it's chosen when your partner needs to sign a mortgage document on DocuSign and the page breaks because of an email tracking redirect (click.docusign.net).
- The DocuSign Problem: Native Unbound requires editing config files and reloading. AdGuard has an untimed global toggle that someone turns off and forgets for days. Blocky has a REST API and a web UI (
blocky-ui) where anyone can click "Disable blocking for 5 minutes". It automatically re-arms itself. - PostgreSQL HA: In anycast, queries spray across all three nodes via ECMP. AdGuard and Pi-hole fragment logs across three local SQLite databases. Blocky streams query logs directly to PostgreSQL (in my case, a CloudNativePG HA cluster on Kubernetes). Full cross-node search across a month of history in milliseconds.
- Decoupled Architecture: Blocky handles the Go filtering proxy (96% block rate with HaGeZi Pro/TIF + StevenBlack) and web UI; a local Unbound on loopback (
127.0.0.1:5335) handles recursive DNSSEC and RFC 8767 stale cache.
I wrote up the full 22-minute deep dive with all the architectural diagrams, failover logs, MikroTik BGP/BFD configs, and the coordinated omission benchmark bugs here if you want to replicate it:
Source: r/homelab · by /u/ProfessionalKing3430