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
- AdGuard Home: Defaults changed the experiment before performance did. In this test, adblock-style rules returned REFUSED until I selected Null IP mode; its default per-client rate limit also shapes a benchmark. A refusal or a throttled query needs interpretation before it becomes a blocking score. I measured 59 MB idle process RSS and 523 MB used disk, with about three minutes to a working filter.
- Pi-hole: The installer needed attention: it asked yes/no prompts and picked defaults I would review. I answered the prompts for this temporary test; those defaults need deliberate configuration before a lasting deployment. Its 100% raw blocking result was expected because the test domains came from its enabled StevenBlack list. It used less process memory here, 9 MB RSS, but more disk, 611 MB, and took about four minutes to reach a working filter.
- Unbound: A resolver is not automatically an ad blocker. Full recursion removes a central forwarding provider, but it still exchanges queries with authoritative servers; query-name minimization limits disclosure. The project overview explains this distinction.
- PowerDNS: I need to judge the component the installer actually deploys. An authoritative nameserver does a different job from filtering a client’s DNS lookups.
- nebula-sync: I found a configuration replicator in the same category as the resolvers. It becomes relevant after choosing Pi-hole and deciding to run more than one instance.
- AdGuardHome-Sync: I would consider this for multiple AdGuard Home instances, not as a competing DNS filter.
- NextDNS CLI: Running a client locally does not make the filtering service self-hosted. The cloud service remains responsible for resolving and filtering queries.
- CoreDNS: I can build DNS behavior from plugins, but its default installer configuration does not deliver the filtering job I am comparing.
- Blocky: I can get a focused filter with versioned configuration files. Whether I prefer that to a UI is an administration choice rather than a blocking score.
- Technitium DNS: I can combine filtering with a broader DNS server, but that expands the scope beyond adding a filter.
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.