Greg Ross

Adblock and DNS: choosing a filter to run myself

Created

What I use today

My starting point is a filtering DNS service. I wanted to find out whether a filter I run myself would add blocking, or whether control over lists, exceptions and query logs would be the more useful gain.

The test server could not reach that service, so I used an unfiltered public resolver as the upstream for both candidate runs and the controlled baseline. I measured the current filtering service separately over a different network path. Its blocking results provide a reference; its timing is not comparable with the candidate runs.

If I forward a local filter’s queries to a filtering provider, I keep both layers. A local filter can block additional domains, but its allow rules cannot undo a block returned by the upstream. Replacing that upstream’s filtering and adding a filter in front of it are different choices. The AdGuard Home configuration documentation explains upstream forwarding and routing selected domains to a different resolver.

The tools

I used the Community Scripts Adblock and DNS category as the candidate list on 2026-10-10. It contained ten entries, including two synchronization add-ons and two scripts marked as testing. Category membership identifies what I review; it does not mean every entry solves the same problem.

The table summarizes the desk review dated 2026-10-10. Resource figures are the scripts’ default Debian allocations, not measured consumption. Network-wide filtering assumes devices use the chosen DNS service. Versions describe the upstream projects; a testing label describes the Community Scripts installer.

Tool Role and network-wide filtering Fit with a router resolver and filtering upstream Upkeep Default CPU / RAM / disk Privacy Project snapshot My shortlist
AdGuard Home DNS filter with a web UI; yes Local-name forwarding and configurable upstreams; provider filtering remains layered when retained Low; one binary and a UI 1 / 512 MB / 2 GB Local logs; forwarded queries reach the upstream Stable v0.107.79, 2026-08-18; active beta line; repo Test: focused filtering with a UI
Pi-hole DNS sinkhole with a web UI and API; yes Same layered pattern; installer upstream defaults need reviewing Low to medium; several components updated together 1 / 512 MB / 2 GB Local logs; forwarded queries reach the upstream Core v6.4.3, 2026-07-06; FTL v6.7.1, 2026-09-19; repo Test: established ecosystem and a different DNS engine
Technitium DNS DNS server with filtering, recursion and zones; yes Can retain the router for local names or take on more DNS responsibilities Low to medium; .NET runtime adds a dependency 1 / 512 MB / 2 GB Local logs; query visibility depends on forwarding or recursion v15.6.0, 2026-10-03; repo Runner-up; broader scope than I need for filtering alone
Blocky DNS proxy with file-based block lists; yes Configurable forwarding; suitable for versioned configuration Low; one binary, YAML configuration 1 / 512 MB / 2 GB Optional local query logs; upstream receives forwarded queries v0.35.0, 2026-09-05; repo Lean alternative; no configuration UI
Unbound Validating recursive resolver; no ad blocking in the default script Resolver building block rather than a ready filtering service Low 1 / 512 MB / 2 GB Full recursion avoids a central forwarding provider; authoritative servers still receive queries v1.26.1, 2026-09-16; repo Useful DNS infrastructure; not my filtering shortlist
CoreDNS Plugin-based DNS server; no default ad blocking Default forwarding and caching need additional filtering configuration Low for the default setup 1 / 256 MB / 2 GB Default script forwards unencrypted DNS to a public resolver v1.14.7, 2026-08-19; repo A server framework rather than a ready filter
PowerDNS The script installs an authoritative server and an administration UI; no filtering resolver A different DNS job from blocking client lookups Medium; multiple components 1 / 1024 MB / 4 GB Authoritative serving is a different role from forwarding browsing queries Active repository; authoritative release date not verified in this review; repo Outside this filtering shortlist
NextDNS CLI Client for cloud filtering profiles; yes Uses a cloud filtering service rather than retaining the existing filtering upstream Low; requires a service account and profile 1 / 512 MB / 2 GB Cloud provider receives queries; client reporting depends on configuration v1.47.3, 2026-06-03; installer marked testing; repo A cloud option rather than a filter I host myself
nebula-sync Replicates Pi-hole configuration; no resolver of its own Relevant to multiple Pi-hole instances Low; depends on existing instances Add-on; no standalone resolver allocation Configuration replication rather than DNS resolution v0.11.2, 2026-05-05; installer marked testing; repo Revisit only if I need replicas
AdGuardHome-Sync Replicates AdGuard Home configuration; no resolver of its own Relevant to multiple AdGuard Home instances Low; depends on existing instances Add-on; no standalone resolver allocation Configuration replication rather than DNS resolution v0.9.4, 2026-10-09; repo Revisit only if I need replicas

I shortlisted AdGuard Home and Pi-hole because both directly address filtering with a UI, while using different implementations. Technitium remains a plausible choice for a wider DNS role; Blocky remains a plausible choice for file-based administration. The stable installer repository and development installer repository document the installation side of this comparison.

Results

On 2026-10-10 I tested the two shortlisted tools in temporary containers with a fixed list of known ad/tracker and normal domains: 100 in each group, versioned 2026-10-10. Normal domains included content delivery, authentication and other service endpoints that should remain reachable. I used A queries and two passes per resolver. This small, selected sample measures DNS behavior; it does not measure how many ads disappear from web pages.

The sample favors Pi-hole. The 100 ad/tracker domains were selected from StevenBlack hosts, which was also Pi-hole’s only enabled list. Its 100% raw blocking result is close to built in. AdGuard Home’s 98% used the separate AdGuard DNS filter and is the more independent result on this sample. These figures do not establish general blocking superiority.

I queried each resolver explicitly. The controlled baseline used the same unfiltered public upstream as both candidates, from the same test client. Only 79 ad domains resolved there in the first pass and 80 in the warm pass; I excluded the remaining 21 and 20 errors from candidate-only credit. All 100 normal domains resolved at that baseline. Errors, including timeouts, remained separate from blocks.

Each timing cell gives median / p95 in milliseconds, with its sample count in parentheses. Candidate cold means its own cache was cleared; the upstream’s cache remained outside my control. The baseline first pass is therefore unverified, rather than a cold measurement.

Resolver Pass Raw ad blocks / 100 (%) Blocks / upstream-resolved ads Normal false blocks Errors: ads / normal Normal-resolved median / p95 (n) Blocked median / p95 (n)
Unfiltered public baseline First; cache unverified 0 (0%) 0 / 79 0 / 100 21 / 0 28 / 36 (100) No blocked replies
Unfiltered public baseline Warm attempt 0 (0%) 0 / 80 0 / 100 20 / 0 28 / 39 (100) No blocked replies
AdGuard Home Candidate cache cold 98 (98%) 78 / 79 0 / 100 1 / 0 28 / 34 (100) 0 / 1 (98)
AdGuard Home Warm 98 (98%) 79 / 80 0 / 100 1 / 0 0 / 1 (100) 0 / 1 (98)
Pi-hole Candidate cache cold 100 (100%) 79 / 79 0 / 100 0 / 0 28 / 37 (100) 0 / 1 (100)
Pi-hole Warm 100 (100%) 80 / 80 0 / 100 0 / 0 0 / 1 (100) 0 / 1 (100)
Current filtering reference; different network path First; cache unverified 100 (100%) 79 / 79 intersected with baseline set 0 / 99 answered 0 / 1 Not comparable; omitted Not comparable; omitted
Current filtering reference; different network path Warm attempt 100 (100%) 80 / 80 intersected with baseline set 0 / 99 answered 0 / 1 Not comparable; omitted Not comparable; omitted

The reference rows are a separate blocking observation, not the candidates’ upstream baseline. They show the filtering DNS service already blocked all 80 ad domains that resolved at the unfiltered upstream in the warm pass. Neither candidate added coverage over the current filtering service on this set. Any reason to host one is control over lists, per-client rules and logs. The reference had one normal-domain timeout in each pass, involving different domains; neither was counted as a false block.

AdGuard Home’s one ad-domain error in each pass was a response without an address, also excluded by the upstream baseline. Its unblocked, upstream-resolving ad domain accounted for the remaining difference. Both candidates resolved all 100 normal domains. I would need a larger, independently selected sample to assess false blocks or coverage beyond this result.

Tool Idle process RSS (MB) Idle container memory used (MB) Used disk (MB) Installer duration (minutes) Approximate time to a working filter (minutes)
AdGuard Home 59 83 523 1.57 3
Pi-hole 9 41 611 2.72 4

I measured idle resources more than five minutes after the last query. RSS measures the DNS process; container memory is a broader measure. Both containers had one CPU, 512 MB allocated RAM and 2 GB allocated disk. Installer duration excludes the extra configuration needed to reach a working filter; working-filter times are approximate.

I tested AdGuard Home v0.107.79 with the AdGuard DNS filter and Null IP blocking. I tested Pi-hole core v6.4.3, web v6.6 and FTL v6.7.1 with StevenBlack hosts only and NULL blocking. Both used the same unfiltered upstream. DNSSEC stayed on for AdGuard Home and off for Pi-hole, following their defaults, so these timings describe the tested configurations rather than an isolated comparison of DNS engines.

I cleared AdGuard Home’s cache through its API and Pi-hole’s cache by restarting its DNS process. Candidate-cold normal lookups had a 28 ms median; warm replies had a 0 ms median and 1 ms p95. Blocked replies were also local and measured 0 / 1 ms. I kept these timings separate so a filter answering more local blocks would not appear faster at normal lookups. Zero milliseconds reflects the timing tool’s integer-millisecond resolution.

I ran one comparison per candidate on one date. Encrypted upstream transport, IPv6 AAAA queries, per-client rules and deployment behind a router were not tested. Integration fit remains reasoning rather than a demonstrated deployment. The temporary containers were removed after the test.

What I learned

Decision

Decision (2026-10-10): I keep the filtering DNS service I already use and don’t host a filter. All three resolvers blocked 98-100% of a sample drawn from one list, so this test can’t rank their coverage; it only shows that a local filter would add nothing I could measure here. The verdict rests on control and running cost instead: the service I use already filters with no false blocks on this sample and costs me no service to maintain.

What would change the verdict: wanting per-device rules, my own block lists and exceptions, or a query log I control. Then I would pick AdGuard Home: one binary with a UI and the more independent result here (98% with its own list). Its documentation lists encrypted upstreams (DNS-over-HTTPS and DNS-over-TLS); I didn’t test them. Pi-hole is the close second, and the better fit if I want its ecosystem of lists.

What adopting one would change:

Change Why it matters
The router’s DHCP hands out the local filter as every device’s DNS server A router change; until then nothing uses the filter
A local filter in front of an upstream filtering service The two filters stack, so a local allow rule can’t undo a block upstream
It has to sit where clients and the upstream can both reach it Placement matters more than the software
Every device’s DNS then depends on one small container It needs a backup, a health check, and a fallback plan for when it is down

Running either long term is a separate change with its own plan; this comparison only measured them.