A sane personal allow / deny-list strategy
Almost everyone who runs their own resolver hits the same wall within the first week. You add a domain to your blocklist, you flush the cache, you query it again — and it resolves anyway, cheerfully, as if you'd said nothing. You re-read your rule. The syntax is fine. The domain is spelled correctly. And yet the block does not stick. The instinct is to assume the resolver is broken. It almost never is.
What is actually happening is a precedence problem. A resolver that does filtering does not evaluate your rules as a flat list where the last word wins. It runs them through an ordered chain, and some mechanisms are strictly stronger than others. Once you internalize that order, the mysterious "I blocked it and it still works" bugs disappear, and you can build a small, intentional set of lists that behave exactly as you expect. This article is about that mental model and the handful of habits that keep your lists sane.
Three tools, not one
People talk about "the blocklist" as if filtering were a single list of bad domains. In practice you have three distinct instruments, and they do genuinely different things. Confusing them is the root of most frustration.
| Tool | What it does | Typical syntax |
|---|---|---|
| Allowlist exception | Marks a domain as never-block, overriding any block rule that would otherwise match it | @@||example.com^ |
| Blocklist rule | Refuses to resolve a domain, returning a sinkhole answer or an empty record | ||ads.example.com^ |
| Rewrite | Replaces the answer entirely — points a name at an IP you choose, or at nothing | tracker.example.com → 0.0.0.0 |
The allowlist and the blocklist are both filter rules. They live in the same chain and argue with each other according to fixed precedence. The rewrite is a different animal: it does not participate in the filter argument at all. It sits in front of it and answers before the filter chain ever gets a vote. That structural difference is the whole story.
The precedence order, in plain terms
When a query arrives, the resolver walks a fixed pipeline. The exact internals vary, but the evaluation order that matters to you looks like this, strongest first:
- Rewrites. If a rewrite matches, its answer is returned and nothing below runs. This is a short-circuit, not a vote.
- Allowlist exceptions. An
@@exception marks the name as permitted. Any block rule found later in the chain loses to it. - Blocklist rules. Your deny lists, plus any subscribed feeds. First match here blocks — unless an allowlist exception already cleared the name.
- Service blocks. The "block this whole platform" toggles (a social network, a streaming service). Evaluated after generic blocklists.
- Built-in protections. Safe-search enforcement, parental categories, safe-browsing — local enforcement that can also rewrite a response server-side.
Read that order twice, because it explains nearly every surprise. An allowlist exception (level 2) is evaluated before, and beats, every blocklist rule, service block and built-in (levels 3–5). A rewrite (level 1) beats all of them, including the allowlist, because it answers before the chain even begins.
The single most useful sentence: an exception beats a later block, and a rewrite beats everything. If you remember nothing else, remember the direction of those two arrows.
The classic gotcha: a deny rule can't beat an allow
Here is the exact scenario that sends people to the support forum. A domain is on an allowlist — maybe you added it yourself months ago, maybe it arrived through a curated allowlist your deployment subscribes to. Later, you decide you actually want that domain blocked. So you add it to your personal blocklist.
Nothing happens. The domain keeps resolving.
This is not a bug. It is precedence doing exactly what it promises. The allowlist exception lives at level 2; your new blocklist rule lives at level 3. Level 2 wins. Adding more block rules, or making them more specific, or refreshing the lists, changes nothing — they are all level 3, and they are all losing the same argument to the same exception.
| You want to… | Domain is currently… | Does it work? |
|---|---|---|
| Block it with a blocklist rule | On an allowlist | No — the exception wins |
| Block it with a blocklist rule | Not allowlisted | Yes |
| Block it with a rewrite to a sinkhole | On an allowlist | Yes — rewrite short-circuits |
| Unblock something a feed blocks | On a blocklist feed | Yes — add an allowlist exception |
So when you genuinely need to block a name that is allowlisted, you have two clean options:
- Remove the allowlist entry if it is yours to remove. This is the right fix when the allowlisting was a mistake or no longer needed. If the entry comes from a shared curated allowlist, removing it locally may not be possible — and editing the shared list affects everyone who uses it.
- Use a rewrite to point the name at a sinkhole such as
0.0.0.0. The rewrite short-circuits the chain above the allowlist, so it wins regardless of any exception. This is the local, surgical fix when you cannot or should not touch the allowlist.
# The deny rule that quietly fails because the name is allowlisted:
||news.example.net^
# The rewrite that actually wins, because it short-circuits the chain:
news.example.net → 0.0.0.0
When to reach for each tool
Precedence tells you which tool can win. Intent tells you which tool you should use. They are not the same question, and reaching for a rewrite every time because "it always wins" is a good way to build a config nobody can reason about later.
Use a blocklist rule when
You want to deny a domain and nothing is fighting you for it. This is the default and should cover the large majority of your deny needs. Blocklist rules are cheap, legible, and they compose well with subscribed feeds. Reach for a heavier tool only when a blocklist rule demonstrably does not take effect.
Use an allowlist exception when
A feed or category is blocking something you need — a vendor portal, an update server, a domain a piece of software refuses to work without. Add the narrowest exception that fixes the problem. An exception is a scalpel; treat over-broad exceptions as a smell.
Use a rewrite when
You need to win against an allowlist, or you need to point a name somewhere specific rather than just refuse it. Rewrites are the right tool for redirecting an internal hostname to a local IP, sinkholing a name that an allowlist protects, or pinning a domain to a known-good address. Because a rewrite short-circuits everything, it is also the easiest tool to forget you created — which is the next problem.
Keep your lists small and intentional
The temptation, once you have a resolver of your own, is to accumulate. A blocklist entry here, an exception there, a rewrite you added during a debugging session at midnight and never removed. Six months later you have a config that nobody — including you — can fully explain, and a domain that mysteriously behaves differently from every other domain because of a forgotten level-1 rule.
A few habits keep this from happening:
- Prefer feeds for breadth, personal rules for specificity. Subscribed, curated threat-intelligence feeds handle the long tail of malware and tracking domains far better than a list you maintain by hand. Reserve your personal lists for the handful of decisions that are specifically yours.
- Comment every personal rule. A rule without a reason is a rule you will be afraid to delete. "Added 2026-03 — vendor portal needs this CDN" is worth more than the rule itself.
- Audit exceptions and rewrites quarterly. These are the two categories that override your filtering. They are exactly the ones that should be smallest and most scrutinized. If you cannot say why an exception exists, that is your signal to test removing it.
- Resist the broad allow. Allowlisting a whole apex domain to fix one subdomain routes the entire domain around your filtering. Allow the subdomain you actually need, not its parent.
Small lists are not just tidier. They are the only kind you can reason about when something breaks at 2 a.m. A blocklist with twelve intentional entries is debuggable. A blocklist with three hundred entries you copied from a forum is a liability with a side of plausible deniability.
Verify the rule actually took effect
The last habit is the one people skip, and it is the one that would have saved them the forum trip. After you add or change a rule, check that it did what you meant — against your own resolver, not against a public one, and with the cache out of the way.
The cache matters more than people expect. If a name was answered a minute ago, your resolver may still be holding that answer with a live TTL, and your shiny new rule will not appear to do anything until the TTL expires. Clear the cache (or wait it out) before you conclude the rule failed.
# Query your own resolver directly, short answer only.
# Replace 192.0.2.53 with your resolver's address.
dig @192.0.2.53 ads.example.com +short
# A sinkholed / blocked name typically returns 0.0.0.0,
# an empty answer, or NXDOMAIN depending on your blocking mode.
# Confirm an allowlist exception worked — the name should
# resolve normally again, not return a sinkhole answer:
dig @192.0.2.53 vendor-portal.example.com +short
# Compare against what an upstream returns, to see whether
# your resolver is rewriting the answer or just passing it through:
dig @203.0.113.9 ads.example.com +short
That last comparison is the quiet workhorse of DNS debugging. If your resolver returns a sinkhole and an external resolver returns the real IP, your rule is working. If both return the same real IP, your rule is not taking effect — and now you know to go looking up the precedence chain for an allowlist exception or a missing rewrite, rather than rewriting the same losing block rule a fourth time.
dig @your-resolver the name. Ten seconds of verification beats an hour of wondering whether the rule "should" have worked.Putting it together
A sane personal list strategy is not a longer list. It is a clearer one. Lean on curated feeds for the broad, boring work of blocking known-bad domains. Use your personal blocklist for the specific deny decisions that are yours to make, and keep it small enough that every entry has a reason next to it. Reserve allowlist exceptions for the narrowest possible "this needs to work" cases, and audit them, because they override your filtering. And keep rewrites for the two jobs only they can do: winning against an allowlist, and pointing a name somewhere specific — knowing each one is a rule that silently ignores everything below it.
Filtering rules don't argue democratically. They run in order: a rewrite answers first, an allowlist exception beats a later block, and only then do your blocklists, service blocks and built-ins get their say. Know the order, keep the lists short, and verify against your own resolver — and "I blocked it and it still resolves" stops being a mystery and starts being a one-line fix.
Make your lists actually win
Understand the chain order and your block always takes effect.
Deploy UnveilDNS free