Comparative benchmark of 5 DNS sinkhole implementations running as
FROM scratch static binaries on StageX. Each server is tested against
4 upstream providers: Quad9, Cloudflare, Google, and OpenDNS.
Upstream protocol varies by server (DoH for AdGuard Home / Blocky /
Ferrous, plain DNS for Pi-hole, DoT for NUMA). DNSSEC validation is
enabled on all servers except Blocky (upstream limitation).
dnsperf runs on the client machine (laptop); the servers run
on a remote VPS — the two are on different networks with no LAN
adjacency, connected over the public internet.
Generated: 2026-07-01T21:33:06Z
Read before ranking: this is a real-world WAN benchmark, not a lab-isolated CPU microbenchmark. The harness and containers are reproducible; absolute QPS and latency include public-internet path, VPS scheduling, upstream resolver policy, and transient network conditions. Treat close results as ties, not universal truth.
Each metric below has two modes, toggled by the filter pills in the nav bar:
Best-of-breed — each server uses its optimal upstream protocol
(DoH, DoT, or plain DNS). This is the real-world deployment scenario.
Common-denominator — all servers are forced to plain UDP 53,
leveling the transport layer to isolate server-side performance differences.
⚠ Transport protocol confound. Each server uses its native upstream protocol
in best-of-breed mode: DoH (AdGuard Home, Blocky, Ferrous),
DoT (NUMA), plain DNS (Pi-hole). Differences in QPS/latency between
servers partly reflect protocol overhead, not just server efficiency.
The "common-denominator" filter (mode=1) forces all servers to plain
UDP 53 to isolate server-side performance. See the
Methodology section for details.
📌 Example: AdGuard Home in best-of-breed mode.
In mode=0, AdGuard Home terminates DoH (DNS-over-HTTPS), which adds
significant TLS handshake and HTTP/2 overhead. This inflates latency
(e.g., ~212 ms vs ~94 ms for plain DNS on cache-hit) and memory
(~287 MB vs ~45 MB) compared to its own mode=1 numbers. This is
not a server-efficiency problem — it is the cost of HTTPS transport.
Switch to "Common-denominator" mode to see server-level performance
without the protocol confound.
How to read this page
Charts on top, data tables below — click "Show data tables" under each scenario to see raw numbers
Violin plots (QPS) — each blob shows every per-second QPS measurement from all 3 runs. Wider = more consistent, taller = faster
Bar charts (Latency, Lost %, Memory) — each bar is the mean over 3 replicates; thin whiskers show the min-to-max replicate range. Hover for exact values
Total Qrys — average queries sent per 30s run across all 3 replicates (varies because slower servers complete fewer queries in the fixed test window)
Base Mem — RSS baseline in MB (peak omitted — indistinguishable from baseline for 30s runs)
Comparison Table
Scenario: Cache hit
All queries resolve from server cache — measures peak throughput after cache and connection warmup.
Every query requires upstream resolution — measures cold-cache behavior plus upstream resolver/network behavior. In common-denominator mode some servers report near-zero QPS when plain-DNS upstream forwarding fails or times out.
Domains on the sinkhole blocklist — measures blocklist behavior. Ferrous DNS is not apples-to-apples here because config-file filter-list blocking is not equivalent to the other servers.
Note: Blocky does not perform DNSSEC validation
(upstream limitation).
All other servers have DNSSEC validation enabled.
Results reflect testing against 4 upstream providers:
Quad9, Cloudflare, Google, and OpenDNS.
Transport Protocol Confound
Each server ships with its preferred upstream protocol baked into the
configuration — there is no single "best" protocol that all servers
support uniformly:
DoH (DNS over HTTPS): AdGuard Home, Blocky, Ferrous DNS
DoT (DNS over TLS): NUMA DNS
Plain DNS (UDP 53): Pi-hole FTL
Best-of-breed mode tests each server with its native protocol —
this is the real-world deployment scenario, but comparisons between
servers partly reflect protocol overhead, not just server efficiency.
Common-denominator mode forces all servers to plain UDP 53 where
possible, reducing the transport confound but not making the implementations
architecturally identical. Pi-hole is plain DNS in both modes because FTL v6
has no native DoH/DoT upstream support.
Dataset completeness
The expected matrix for observed cells is 480 rows. The published CSV has 478 rows.
Aggregates use available rows; missing rows are not imputed.
ferrous/opendns/blocked/1 has 1 replicate(s) instead of 3
Fairness matrix
Feature
Pi-hole
Blocky
AdGuard Home
Ferrous
Numa
Best-mode upstream
Plain UDP
DoH
DoH
DoH
DoT
Common mode
Plain UDP
Plain UDP
Plain UDP
Plain UDP
Plain UDP
DNSSEC
Local enabled
Upstream only
Enabled
Enabled
Enabled
Cache enabled
Yes
Yes
Yes
Yes
Yes
Rate limit
Raised high
Not configured
Disabled
Disabled
Not configured
Blocked scenario
NULL response
NXDOMAIN
Filter config
Not equivalent
File blocklist
Warmup
Before each scenario, a 5-second dnsperf warmup pass is run
to stabilize connections and fill the TCP congestion window. For
cache-hit and mixed scenarios, the server cache is also pre-warmed
by resolving each cached domain once via dig before the warmup
pass. The measurement window (-l 30) begins only after both
warmup phases complete. Each scenario therefore runs for ≈37–42s
wall-clock; only the final 30 s of steady-state data are reported.
Test Parameters
Each scenario runs for 30 seconds with 10 concurrent clients
Replicate counts are computed from the generated CSV; incomplete cells are disclosed above
Only coarse EMEA geography is disclosed; provider, exact region, IPs, and exact run timestamps are private.
The published run uses fixed order, not randomized order.
Three replicates per cell are practical, not definitive; close results should be treated as noise.
dnsperf average latency hides tail latency; p50/p95/p99 are unavailable in this dataset.
Memory is a benchmark-time reading, not a full memory profile.
Reproducibility
All builds are 100% StageX, FROM scratch, fully static binaries with
pinned source tarballs and SHA256 verification. See the
project README
for rebuild instructions, schema, and caveats.
Finally, thanks to ZeroClaw and opencode for helping drive the coding workflow, and to ThePrimeagen (GitHub), whose videos were running in the background while this project was built.
⚠ Chart library failed to load. Please check your internet connection or try a different browser.
Charts require JavaScript. Data tables below still work.