UnveilTech

UnveilDNS Blog

← All articles

Try UnveilDNS free

A sane personal allow / deny-list strategy

Posted 2026-06-09 · 7 min read · filtering

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.

ToolWhat it doesTypical syntax
Allowlist exceptionMarks a domain as never-block, overriding any block rule that would otherwise match it@@||example.com^
Blocklist ruleRefuses to resolve a domain, returning a sinkhole answer or an empty record||ads.example.com^
RewriteReplaces the answer entirely — points a name at an IP you choose, or at nothingtracker.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 shorthand. Allowlist and blocklist are filters that debate each other. A rewrite is not a filter — it short-circuits the debate and answers directly. Keep that distinction and most of this article follows from it.

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:

  1. Rewrites. If a rewrite matches, its answer is returned and nothing below runs. This is a short-circuit, not a vote.
  2. Allowlist exceptions. An @@ exception marks the name as permitted. Any block rule found later in the chain loses to it.
  3. Blocklist rules. Your deny lists, plus any subscribed feeds. First match here blocks — unless an allowlist exception already cleared the name.
  4. Service blocks. The "block this whole platform" toggles (a social network, a streaming service). Evaluated after generic blocklists.
  5. 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 ruleOn an allowlistNo — the exception wins
Block it with a blocklist ruleNot allowlistedYes
Block it with a rewrite to a sinkholeOn an allowlistYes — rewrite short-circuits
Unblock something a feed blocksOn a blocklist feedYes — add an allowlist exception

So when you genuinely need to block a name that is allowlisted, you have two clean options:

# 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.

Power has a cost. A rewrite outranks your entire filter chain. That makes it the perfect override and the perfect footgun. Every rewrite you add is a rule that silently ignores all of your filtering for one name. Use them deliberately, and write down why each one exists.

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:

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.

A two-line ritual. Every time you add a rule: clear the cache, then 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