RPZ explained: Response Policy Zones without the BIND headache
Most people meet Response Policy Zones the hard way: a security vendor hands them a feed URL, the documentation says "load it as an RPZ", and suddenly they are reading about zone transfers, response-policy clauses, and why their resolver logged a SERVFAIL at 3 a.m. RPZ is one of the most useful ideas in DNS — a firewall you express as a zone file — but the classic implementation wraps it in enough operational ceremony that plenty of teams quietly give up and paste the domains into a hosts file instead.
That is a shame, because the concept is genuinely elegant. RPZ lets a policy author say "when anyone asks about this name, or resolves to that address, or is delegated through those nameservers — rewrite the answer." It is a standardized way to distribute and apply DNS policy, and threat-intelligence providers have leaned on it for over a decade. This article explains what RPZ actually is, the actions and triggers it supports, why feeds ship in this format, and how a modern resolver can swallow an RPZ list without forcing you to run a full BIND zone-transfer pipeline.
What RPZ actually is
A Response Policy Zone is, mechanically, an ordinary DNS zone — the same SOA, NS, and resource-record structure you would write for any domain. The twist is in interpretation: the resolver is told to treat this zone not as authoritative data to serve, but as a policy ruleset to consult before answering a query. The format was introduced by Paul Vixie and Vernon Schryver around 2010 and documented in an informational draft (commonly referenced as the "DNS RPZ" format). It has been a de facto standard ever since.
Because a policy is encoded as a zone, it inherits everything DNS already knows how to do: it can be transferred between servers (AXFR/IXFR), signed, versioned through the SOA serial, and updated incrementally. A vendor publishes a zone, your resolver pulls deltas, and policy propagates the same way DNS data propagates. That reuse is the whole appeal — and, as we will see, also the source of the operational pain.
The action types
When a query matches a rule, RPZ rewrites the response. There are five canonical actions, each expressed through a specific record value in the policy zone.
| Action | Encoded as | Effect on the client |
|---|---|---|
| NXDOMAIN | CNAME to the root (.) | The name "does not exist" — the most common block. Clients see a clean negative answer. |
| NODATA | CNAME to a wildcard (*.) | The name exists but has no records of the requested type. Useful to suppress, say, AAAA without killing the name entirely. |
| Passthru (allow) | CNAME to rpz-passthru. | Explicitly exempt the name from policy. This is how you whitelist an entry that a broader rule would otherwise catch. |
| Redirect (CNAME) | CNAME to a real target name | Resolve the query to a different name — a walled-garden page, a sinkhole, a safe alternative. |
| Drop | CNAME to rpz-drop. | Discard the query with no response at all. The client times out. Useful against abusive floods you do not want to acknowledge. |
A few subtleties matter in practice. A literal local-data answer (returning a specific A/AAAA record instead of a CNAME) is also possible and behaves like a redirect to an address. And ordering is significant: passthru rules are how you carve exceptions out of a blanket block, so a more-specific allow must be evaluated before the broad deny — which RPZ handles by zone precedence and longest-match semantics.
# Illustrative RPZ fragment (owner name = trigger, data = action)
malware.example. CNAME . ; NXDOMAIN — block
ads.example. CNAME *. ; NODATA
trusted.example. CNAME rpz-passthru. ; allow / whitelist
phish.example. CNAME safe.example. ; redirect to a notice page
flood.example. CNAME rpz-drop. ; drop silently
The trigger types
Actions answer "what to do." Triggers answer "what to match against." RPZ supports four, and this is where it gets more powerful than a flat domain list. Each trigger lives in its own naming convention inside the policy zone.
| Trigger | Matches on | Typical use |
|---|---|---|
| QNAME | The queried name itself | The bread-and-butter case. "Block bad.example and everything under it." |
| Response IP | The address in the answer | "Block any name that resolves into this hostile netblock," even names you have never seen. |
| NSDNAME | The nameservers serving the answer | "Block anything delegated through this known-bad nameserver." Catches whole malicious infrastructures at once. |
| NSIP | The IP of the answering nameservers | Like NSDNAME, but keyed on the nameserver's address rather than its name. |
Vendors also distribute a client-IP dimension, letting policy depend on who is asking rather than only on what they ask. That turns a single resolver into a multi-tenant policy engine: one client subnet gets a strict zone, another gets a lenient one. The response-IP and nameserver triggers are the genuinely clever part — a plain blocklist can only react to names it already knows, whereas an IP trigger blocks a brand-new phishing domain the moment it resolves into a flagged hosting range.
QNAME blocking is reactive: you must already know the bad name. Response-IP and NSDNAME blocking are structural: you block the infrastructure, so freshly minted domains on the same hosts and nameservers are caught on first contact.
Why security vendors ship RPZ feeds
If you subscribe to commercial threat intelligence, there is a good chance at least one feed is offered as RPZ. The reasons are practical:
- It is a standard. One format consumed by many resolver implementations. The vendor writes the zone once; every customer applies it the same way.
- Updates are incremental. Because the feed is a zone with a serial number, customers pull only the delta (IXFR) instead of re-downloading megabytes hourly. Newly observed malicious domains propagate in seconds.
- It carries intent, not just names. A hosts file says "block." An RPZ feed can say "block this name, sinkhole that one, and exempt this false-positive" — policy and data travel together.
- Triggers add reach. A response-IP rule lets a feed cover an attacker's whole hosting footprint without enumerating every domain, which matters against fast-rotating campaigns.
So RPZ became the lingua franca for "here is some DNS policy, apply it." The problem was never the feed. It was the machinery you needed to consume it.
The operational tax in classic BIND
The traditional way to use RPZ is to configure a response-policy statement and either host the zone locally or pull it via zone transfer from the provider. That works, and on a well-run BIND it works well. But it drags in a surprising amount of operational surface:
- Zone-transfer plumbing. You configure AXFR/IXFR from the feed's primaries, manage TSIG keys, open the right ports, and hope the provider's transfer endpoint is reachable from your resolver. A blocked transfer means stale or missing policy.
- Reloads and rebuilds. Depending on configuration, applying or reordering policy zones can mean a config reload. On a busy resolver that is a moment of risk, and a malformed zone can take down resolution rather than just one rule.
- Ordering and precedence debugging. With multiple policy zones, working out why a name was (or was not) rewritten means reasoning about zone order, longest-match, and passthru exceptions across several files.
- DNSSEC interaction. Rewriting an answer is, by definition, forging it. RPZ and DNSSEC validation have well-known tensions; a rewritten name can fail validation downstream unless you handle the break deliberately.
- Format lock-in. If your resolver is not BIND-family, native RPZ support ranges from partial to absent — yet the feed you bought only ships RPZ.
A modern alternative: ingest, convert, apply
You do not actually need a zone-transfer pipeline to benefit from an RPZ feed. The information in an RPZ list is just rules — owner name plus action — and those map cleanly onto the block and redirect primitives any decent filtering resolver already has. So instead of teaching the resolver to be an RPZ secondary, you teach an ingestion layer to read the RPZ list and translate it into the resolver's native rule format.
That is the approach UnveilDNS takes. You point it at an RPZ source; it fetches the list (validating that the source is reachable and not pointing at private infrastructure), detects the format, converts each rule, and registers the result as a normal blocklist. No response-policy clause, no TSIG, no transfer ports. The list is refreshed on a schedule, and only when the converted output actually changes does the filter engine reload — so a feed that publishes a new serial every few minutes does not translate into a reconfiguration storm on a high-traffic resolver.
Mapping RPZ rules to block/redirect rules
The translation is mostly mechanical. Each RPZ action has a natural equivalent in a name-based rule grammar:
| RPZ rule | Meaning | Equivalent native rule |
|---|---|---|
name CNAME . | NXDOMAIN block | Block name (return "does not exist") |
name CNAME *. | NODATA | Block name (no data for the type) |
name CNAME target. | Redirect | Rewrite name → target |
name A 0.0.0.0 | Sinkhole to a null address | Block name |
name CNAME rpz-passthru. | Allow / whitelist | Allow exception for name |
QNAME triggers convert one-to-one. The redirect case becomes a DNS rewrite to the named target. Passthru becomes an allow exception. The two triggers that do not survive a static conversion are response-IP and NSDNAME/NSIP, because those can only be evaluated at resolution time against a live answer — a converted blocklist is name-keyed by nature. In practice most distributed RPZ feeds are overwhelmingly QNAME rules, so conversion captures the vast majority of the policy; the infrastructure-level triggers are better served by a resolver's own threat-intelligence and detection layers (response-IP reputation, fast-flux and DGA heuristics) than by importing them from a third-party zone.
# Before (RPZ) -> After (native rule, conceptual)
bad.example. CNAME . block: bad.example
go.example. CNAME safe.example. rewrite: go.example -> safe.example
ok.example. CNAME rpz-passthru. allow: ok.example
RPZ feed vs a plain blocklist: when each wins
Once you can consume both, the question becomes which to reach for. They are not interchangeable.
| Choose… | When… |
|---|---|
| An RPZ feed | You are paying for curated threat intelligence that ships in RPZ; you want vendor-managed redirects/sinkholes and passthru exceptions baked in; or you need the infrastructure-level triggers (and run a resolver that evaluates them live). |
| A plain blocklist | You are aggregating community or open lists that are already simple domain or hosts files; you want one flat, auditable list of names; or you only need "block these and don't pretend they redirect anywhere." |
The deciding factor is usually who authors the policy. RPZ shines when a provider expresses nuanced intent — block here, sinkhole there, exempt this — and you want that intent applied faithfully. A plain blocklist shines when you are the policy author and a list of names is all the expressiveness you need. The good news is that with format conversion in front of the resolver, you no longer have to choose your tooling based on which format your feed happens to use. You choose based on the policy you actually want, and let the ingestion layer normalize the rest.
RPZ's reputation as a BIND headache is really a reputation for one specific deployment model. The format itself — a DNS firewall expressed as a zone — is worth keeping. Consume the rules, drop the zone-transfer ceremony, and RPZ becomes just another well-structured input to a filtering resolver that already knows how to block, redirect, and allow.
Use RPZ feeds, skip the pain
UnveilDNS ingests RPZ-format lists and converts them for you — no zone-file surgery.
Deploy UnveilDNS free