What Are Response Policy Zones?
Response Policy Zones (RPZ) is a DNS firewall mechanism built into BIND9 that intercepts DNS queries and applies configurable policies before returning responses to clients. Originally developed by Paul Vixie and Vernon Schryver at ISC (Internet Systems Consortium), RPZ allows DNS administrators to block malicious domains, redirect phishing sites to a sinkhole, enforce acceptable use policies, and integrate threat intelligence feeds — all at the DNS layer, without touching firewall rulesets or endpoint configurations.
At its core, RPZ works by defining a special DNS zone that acts as a policy database. When a client sends a query to your BIND9 resolver, the server checks the query against all configured RPZ zones before performing recursive resolution. If a match is found, the configured action — block, redirect, passthrough, or silent drop — is applied immediately and the synthesized response is returned to the client.
Why DNS Firewalling with RPZ Matters
Blocking threats at the DNS layer offers several operational advantages over traditional IP-based firewall rules or endpoint security agents:
- Early interception: DNS is queried before any network connection is established, stopping command-and-control callbacks and malware downloads before the first packet is sent
- Domain-name awareness: RPZ blocks by fully-qualified domain name, wildcard subdomain patterns, or nameserver associations — not just by IP address, which threat actors rotate constantly
- Centralized enforcement: A single BIND9 resolver with RPZ protects every client on your network without per-host software deployment
- Feed integration: Commercial and open-source threat intelligence providers publish RPZ-formatted zone files that BIND9 can consume via standard zone transfers
- Audit trail: Every RPZ policy match generates a structured log entry, giving security teams a clear record of blocked query attempts for incident response and compliance
Prerequisites
- BIND9 9.8 or later (RPZ support was introduced in 9.8; BIND9 9.11 or later is recommended for full trigger and action support)
- Ubuntu 20.04 or 22.04 with BIND9 installed and configured as a recursive resolver
- Write access to
/etc/bind/named.conf.options
,/etc/bind/named.conf.local
, and the zone file directory - Root or sudo privileges on sw-infrarunbook-01
Verify your installed BIND9 version before proceeding:
named -v
Step 1 – Enable RPZ in the BIND9 Options Block
Open the main BIND9 options file. On Ubuntu this is
/etc/bind/named.conf.options:
sudo nano /etc/bind/named.conf.options
Add the
response-policydirective inside the
options { };block, after your
forwardersand
dnssec-validationsettings:
options {
directory "/var/cache/bind";
forwarders {
10.0.0.1;
10.0.0.2;
};
dnssec-validation auto;
listen-on { 127.0.0.1; 10.10.1.53; };
allow-recursion { 10.10.0.0/16; 127.0.0.1; };
recursion yes;
response-policy {
zone "rpz.solvethenetwork.com" policy given;
};
};
The
policy givendirective instructs BIND9 to honour whatever action is specified in each individual RPZ record within the zone. Other global policy overrides such as
NXDOMAIN,
NODATA,
PASSTHRU, or
DROPforce a single action for every match in that zone regardless of zone record content. The
givensetting is the most flexible choice for production deployments where you need mixed actions across different categories of blocked domains.
Step 2 – Declare the RPZ Zone in named.conf.local
Add the zone declaration to
/etc/bind/named.conf.local. The RPZ zone is a standard primary zone with strict access controls — clients must never be able to query it directly, and it must not be transferable to unauthorized systems:
zone "rpz.solvethenetwork.com" {
type primary;
file "/etc/bind/zones/rpz.solvethenetwork.com.db";
allow-query { none; };
allow-transfer { none; };
};
Create the zones directory and assign the correct ownership:
sudo mkdir -p /etc/bind/zones
sudo chown bind:bind /etc/bind/zones
Step 3 – Create the RPZ Zone File
Create the zone file at the path you declared above. RPZ zone files use standard DNS zone file syntax. The owner names and record types encode policy triggers and actions using special BIND9 conventions:
sudo nano /etc/bind/zones/rpz.solvethenetwork.com.db
Start with a minimal working zone that demonstrates the most common use case — returning NXDOMAIN for known malicious domains:
$TTL 300
@ IN SOA sw-infrarunbook-01.solvethenetwork.com. infrarunbook-admin.solvethenetwork.com. (
2024031501 ; Serial (YYYYMMDDNN)
3600 ; Refresh
900 ; Retry
604800 ; Expire
300 ) ; Negative cache TTL
IN NS sw-infrarunbook-01.solvethenetwork.com.
; ============================================================
; QNAME Triggers -- block queries for specific domain names
; ============================================================
; Block a known malware distribution domain -- return NXDOMAIN
badactor-malware.net CNAME .
; Block all subdomains of a phishing domain -- return NXDOMAIN
*.phish-campaign.ru CNAME .
; Block apex and all subdomains of a C2 domain
cnc-botnet.xyz CNAME .
*.cnc-botnet.xyz CNAME .
; ============================================================
; Walled Garden -- redirect to sinkhole instead of NXDOMAIN
; ============================================================
; Redirect an ad-tracking domain to internal sinkhole at 10.10.9.99
adtracker-network.com A 10.10.9.99
*.adtracker-network.com A 10.10.9.99
The short
$TTL 300at the top applies a 5-minute TTL to all RPZ records. This ensures that when you remove a block entry, resolvers and clients flush the cached policy quickly without requiring a full restart.
Understanding RPZ Trigger Types
QNAME Triggers
The simplest and most widely used trigger. The owner name in the RPZ zone is the domain being queried by the client. When a client queries
badactor-malware.net, BIND9 finds a matching QNAME entry and applies the action immediately. Wildcard entries such as
*.phish-campaign.rumatch all subdomains but do not match the apex itself — this is why production blocklists include both the bare domain and a wildcard entry for complete coverage.
IP Address Response Triggers (rpz-ip)
Triggers on IP addresses returned in DNS responses from the upstream resolver. This is valuable when a threat actor rotates domain names while reusing the same IP infrastructure. The owner name encodes the IP address in reverse-dotted notation, prefixed by the CIDR prefix length and suffixed with
.rpz-ip. The encoding format is:
[prefix-length].[octet4].[octet3].[octet2].[octet1].rpz-ip
; Block any DNS response that resolves to 10.10.5.100 (/32)
32.100.5.10.10.rpz-ip CNAME .
; Block any response within the 172.16.100.0/24 range
24.0.100.16.172.rpz-ip CNAME .
Nameserver Name Triggers (rpz-nsdname)
Triggers based on the authoritative nameserver name for the queried domain. If a domain delegates to a nameserver that is known to host malware distribution infrastructure, you can block all domains under that nameserver regardless of their individual names. The trigger suffix is
.rpz-nsdname:
; Block domains whose authoritative NS is ns1.bulletproof-hosting.bz
ns1.bulletproof-hosting.bz.rpz-nsdname CNAME .
ns2.bulletproof-hosting.bz.rpz-nsdname CNAME .
Nameserver IP Triggers (rpz-nsip)
Similar to
rpz-nsdnamebut triggers on the resolved IP address of the authoritative nameserver rather than its hostname. Useful when hostile nameservers use disposable hostnames but consistent IP infrastructure. IP encoding follows the same reversed-dotted notation as
rpz-ip:
; Block domains whose NS resolves to 10.99.1.1
32.1.1.99.10.rpz-nsip CNAME .
Client IP Triggers (rpz-client-ip)
Triggers based on the IP address of the DNS client sending the query. This enables per-host policy enforcement directly within BIND9. A quarantined workstation can be restricted from resolving external domains, or a specific device can be subject to stricter content filtering. The encoding mirrors
rpz-ipbut uses the suffix
.rpz-client-ip:
; Apply NXDOMAIN to all queries from quarantined host 10.10.1.45
32.45.1.10.10.rpz-client-ip CNAME .
Understanding RPZ Action Types
NXDOMAIN – CNAME .
Returns a Non-Existent Domain response to the client. The queried name appears not to exist. This is the most common RPZ action for blocklists. Clients receive a standard NXDOMAIN and display a domain-not-found error rather than a connection refused, which makes the blocking less obvious to users and avoids browser security warnings that might arise from a refused TCP connection.
malware-distribution.net CNAME .
NODATA – CNAME *.
Returns an empty answer section with an NOERROR rcode. The domain appears to exist but has no records of the queried type. This is useful when NXDOMAIN causes application errors in certain software stacks that treat non-existent domains as fatal and refuse to fall back gracefully:
tracker-domain.com CNAME *.
DROP – CNAME rpz-drop.
Silently discards the DNS query with no response sent to the client. The client resolver retries and eventually times out. Use this action sparingly — it introduces query latency for end users and can cause noticeable slowdowns. DROP is most appropriate for blocking aggressive DNS scanners or probing hosts where you want to deny even the NXDOMAIN information:
dns-scanner-domain.net CNAME rpz-drop.
PASSTHRU – CNAME rpz-passthru.
Explicitly allows a query to bypass all lower-priority RPZ zones and proceed to normal recursive resolution. PASSTHRU is used to whitelist specific domains that would otherwise be caught by a wildcard block or an IP-range trigger. Always configure PASSTHRU entries in a higher-priority RPZ zone than your blocklist zones so the whitelist is evaluated first:
; Allow this host to resolve update servers even if the IP range is blocked
updates.solvethenetwork.com CNAME rpz-passthru.
Local Data – Walled Garden Redirect
Return a specific IP address instead of performing recursive resolution. When a client queries a blocked domain, BIND9 returns your internal sinkhole IP directly. This powers a walled garden where a web server at the sinkhole IP serves an informational page explaining the block, providing far better user experience than a cryptic NXDOMAIN. Provide both an A record and a wildcard A to catch subdomain queries:
phishing-campaign.net A 10.10.9.99
*.phishing-campaign.net A 10.10.9.99
Building a Multi-Policy Production Zone File
A production RPZ zone combines multiple trigger types and actions into a single comprehensive policy file organized by category. The example below represents a realistic deployment for sw-infrarunbook-01:
$TTL 300
@ IN SOA sw-infrarunbook-01.solvethenetwork.com. infrarunbook-admin.solvethenetwork.com. (
2024031502
3600
900
604800
300 )
IN NS sw-infrarunbook-01.solvethenetwork.com.
; --- Category: Malware C2 Domains (NXDOMAIN) ---
cnc-botnet.xyz CNAME .
*.cnc-botnet.xyz CNAME .
exfil-server.cc CNAME .
*.exfil-server.cc CNAME .
; --- Category: Phishing (Walled Garden at 10.10.9.99) ---
login-portal-fake.com A 10.10.9.99
*.login-portal-fake.com A 10.10.9.99
creds-harvest.net A 10.10.9.99
*.creds-harvest.net A 10.10.9.99
; --- Category: Hostile IP Ranges (rpz-ip) ---
; Block responses pointing into a known hostile /24
24.0.55.203.185.rpz-ip CNAME .
; --- Category: Hostile Nameservers (rpz-nsdname) ---
ns1.bulletproof-hosting.bz.rpz-nsdname CNAME .
ns2.bulletproof-hosting.bz.rpz-nsdname CNAME .
; --- Category: Quarantine Policy (rpz-client-ip) ---
; Host 10.10.1.88 is under quarantine -- block all external resolution
32.88.1.10.10.rpz-client-ip CNAME .
; --- Whitelist: sw-infrarunbook-01 bypasses all blocks (rpz-client-ip PASSTHRU) ---
32.53.1.10.10.rpz-client-ip CNAME rpz-passthru.
Configuring RPZ Logging
RPZ policy matches are logged under the
rpzBIND9 logging category. Add or update a logging block in
/etc/bind/named.conf.optionsto capture RPZ hits to a dedicated file:
logging {
channel rpz_log {
file "/var/log/named/rpz.log" versions 7 size 20m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
channel default_log {
file "/var/log/named/named.log" versions 5 size 10m;
severity dynamic;
print-time yes;
print-category yes;
};
category rpz { rpz_log; };
category default { default_log; };
};
Create the log directory and assign ownership to the BIND9 process user:
sudo mkdir -p /var/log/named
sudo chown bind:bind /var/log/named
With this configuration every RPZ policy match writes a structured log line to
/var/log/named/rpz.logcontaining the client IP address, queried name, matched RPZ zone name, and applied action. This log is invaluable during incident response — a spike in RPZ hits from a single internal IP often indicates a compromised host attempting C2 callback.
Validating Configuration and Reloading BIND9
Before reloading, always validate both the named configuration and the zone file to prevent service disruptions on sw-infrarunbook-01:
sudo named-checkconf
sudo named-checkzone rpz.solvethenetwork.com /etc/bind/zones/rpz.solvethenetwork.com.db
If
named-checkconfproduces no output and
named-checkzonereports OK, reload BIND9 without dropping active client connections:
sudo rndc reload
To reload only the RPZ zone after adding or removing entries — faster than a full server reload when only zone data changed:
sudo rndc reload rpz.solvethenetwork.com
Testing RPZ Behavior with dig
Direct test queries at the BIND9 resolver address and verify each action type behaves as expected. Replace
10.10.1.53with the IP on which your resolver is listening:
# Test NXDOMAIN action -- should return status: NXDOMAIN
dig @10.10.1.53 cnc-botnet.xyz A
# Test walled garden -- should return A 10.10.9.99
dig @10.10.1.53 login-portal-fake.com A
# Test wildcard entry -- subdomain should also return 10.10.9.99
dig @10.10.1.53 accounts.login-portal-fake.com A
# Test legitimate internal domain passes through normally
dig @10.10.1.53 solvethenetwork.com A
A successful NXDOMAIN RPZ block returns output similar to the following:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 28471
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; ANSWER SECTION:
cnc-botnet.xyz. 300 IN CNAME .
;; AUTHORITY SECTION:
rpz.solvethenetwork.com. 300 IN SOA sw-infrarunbook-01.solvethenetwork.com. ...
Notice that BIND9 returns the CNAME record from the RPZ zone in the answer section. This is intentional — it allows downstream resolvers and security monitoring tools to identify that the response was RPZ-synthesized rather than returned by the authoritative server for the queried domain.
Using Multiple RPZ Zones with Priority Order
BIND9 evaluates RPZ zones in the order they appear in the
response-policydirective. The first match wins. Use this layering behaviour to place a local whitelist above blocklist zones, or to tier multiple threat feed zones by trust level:
response-policy {
zone "whitelist.rpz.solvethenetwork.com" policy passthru;
zone "rpz-local.solvethenetwork.com" policy given;
zone "rpz-threatfeed.solvethenetwork.com" policy given;
};
With this configuration, the whitelist zone is evaluated first — any PASSTHRU match immediately sends the query to normal recursive resolution, bypassing the local blocklist and the threat feed zone entirely. Declare each zone individually in
named.conf.local.
Integrating an External RPZ Threat Feed as a Secondary Zone
Commercial and open-source threat intelligence providers publish RPZ-formatted zones consumable via authenticated AXFR/IXFR zone transfer. Configure the feed as a secondary zone pointing at the provider's primary server. Use a TSIG key for transfer authentication:
key "rpzfeed-tsig-key" {
algorithm hmac-sha256;
secret "c2VjcmV0a2V5aGVyZWJhc2U2NGVuY29kZWQ=";
};
zone "rpz-threatfeed.solvethenetwork.com" {
type secondary;
primaries { 10.20.0.10 key rpzfeed-tsig-key; };
file "/var/cache/bind/rpz-threatfeed.solvethenetwork.com.db";
allow-query { none; };
allow-transfer { none; };
};
After the initial AXFR completes, BIND9 automatically loads the feed into the RPZ engine. Subsequent IXFR incremental updates are applied without a full reload. Monitor transfer status with:
sudo rndc zonestatus rpz-threatfeed.solvethenetwork.com
Performance Tuning for Large RPZ Zones
RPZ lookup adds a small amount of latency to every query. On modern hardware the overhead is negligible for zones with tens of thousands of entries, but large-scale deployments warrant attention to the following settings:
- TTL tuning: Short TTLs (300s) allow rapid policy propagation but increase zone transfer frequency. For stable, slowly-changing blocklists use longer TTLs (3600s or higher)
- Memory usage: BIND9 holds the RPZ zone in memory. A zone containing 500,000 entries consumes approximately 150–200 MB of RAM. Monitor named memory with
rndc stats
and review/var/cache/bind/named_stats.txt
- qname-wait-recurse: Setting
qname-wait-recurse no;
on a zone allows BIND9 to apply QNAME triggers before recursion completes, reducing blocked-domain query latency at the cost of not evaluating IP or NS triggers for those queries - break-dnssec: By default BIND9 will not override DNSSEC-validated responses with RPZ actions. If you need to block DNSSEC-signed malicious domains, add
break-dnssec yes;
to that zone entry
response-policy {
zone "rpz.solvethenetwork.com" policy given
qname-wait-recurse no
break-dnssec yes;
};
Frequently Asked Questions
Q: What minimum version of BIND9 is required to use RPZ?
A: RPZ was introduced in BIND9 9.8.1. However, many trigger types (including
rpz-client-ipand
rpz-nsip) and the
policy givendirective were added or stabilized in later releases. BIND9 9.11 or later is strongly recommended for full feature parity and security maintenance. Ubuntu 22.04 ships BIND9 9.18 which includes all RPZ capabilities covered in this guide.
Q: Does RPZ work if DNSSEC validation is enabled?
A: Yes, but by default BIND9 will not apply RPZ actions to responses that pass DNSSEC validation. This is a deliberate security design — RPZ cannot silently override a cryptographically valid response unless you explicitly set
break-dnssec yes;on the zone in the
response-policydirective. In practice most malicious domains are not DNSSEC-signed, so this default does not impede typical blocklist deployments.
Q: Can RPZ be deployed on an authoritative-only BIND9 server?
A: No. RPZ only applies to responses generated by the recursive resolver engine. An authoritative-only server that does not perform recursion will never evaluate RPZ policies. RPZ must be configured on a recursive (or recursive+authoritative) BIND9 instance. Attempting to configure
response-policyon a pure authoritative server will have no effect on query handling.
Q: What is the difference between the NXDOMAIN and NODATA RPZ actions?
A: NXDOMAIN (
CNAME .) tells the client the queried domain does not exist at all, returning rcode 3. NODATA (
CNAME *.) tells the client the domain exists but has no records of the requested type, returning rcode 0 with an empty answer section. NODATA is appropriate when an application treats NXDOMAIN as a fatal error and crashes, but handles an empty answer more gracefully. For most browser-based use cases NXDOMAIN is preferred as it produces a clear error page.
Q: How do I whitelist a domain that is being blocked by an RPZ zone?
A: Create a dedicated whitelist RPZ zone with a higher priority (listed first) in the
response-policydirective, and add
CNAME rpz-passthru.entries for the domains to whitelist. Because BIND9 evaluates zones in order and stops at the first match, the PASSTHRU entry in the high-priority whitelist zone preempts any block in a lower-priority zone. You can also set
policy passthruat the zone level to make an entire RPZ zone act as a whitelist.
Q: Will RPZ block affect clients querying my authoritative zones?
A: RPZ policies are evaluated during recursive resolution. Queries for zones for which BIND9 is authoritative are answered directly from the authoritative zone data without going through the recursive engine, so RPZ does not interfere with authoritative responses served by the same BIND9 instance. Internal clients querying for
solvethenetwork.comrecords will always receive the authoritative answer regardless of RPZ configuration.
Q: How do I update RPZ entries without restarting BIND9?
A: Edit the zone file, increment the serial number in the SOA record, then run
sudo rndc reload rpz.solvethenetwork.com. This reloads only the specified zone without interrupting the resolver or dropping any client connections. For secondary (threat feed) zones, BIND9 automatically picks up IXFR updates from the primary when the SOA serial changes, with no manual action required on sw-infrarunbook-01.
Q: Can I use RPZ to block an entire top-level domain?
A: Yes. A wildcard QNAME entry for the TLD itself will match all second-level domains under it. Add both the TLD apex and a wildcard entry to cover all subdomains: use
xyz CNAME .to block the TLD apex and
*.xyz CNAME .to block all domains ending in
.xyz. Be cautious with this approach — broad TLD blocks frequently cause collateral damage by blocking legitimate services that share the TLD.
Q: How do I monitor whether RPZ is actively filtering traffic?
A: With the logging configuration from this guide, every RPZ hit is written to
/var/log/named/rpz.log. You can tail this file in real time with
tail -f /var/log/named/rpz.logto watch live policy matches. Additionally,
rndc statsfollowed by inspecting
/var/cache/bind/named_stats.txtshows cumulative RPZ query counters broken down by zone and action type, useful for dashboarding and alerting.
Q: Does the RPZ zone name appear in client DNS responses?
A: For NXDOMAIN and NODATA actions, BIND9 includes the RPZ zone's SOA record in the authority section of the synthesized response, which reveals the RPZ zone name to the querying client. If you wish to conceal the RPZ zone name, set
add-soa no;in the zone's
response-policyentry. For walled garden (local data) responses, no SOA is added by default and the response is indistinguishable from a normal authoritative answer.
Q: What happens if the RPZ zone file contains a syntax error?
A: BIND9 will fail to load the zone and log an error, but the resolver itself continues running using the last successfully loaded version of the RPZ data (if any). New queries will not be filtered by the broken zone until you fix the syntax error and successfully reload. This is why running
named-checkzonebefore every reload is critical — it catches syntax problems before they affect the running service.
Q: Can multiple RPZ zones contradict each other, and how is that resolved?
A: Yes, multiple zones can contain conflicting entries for the same domain. BIND9 resolves conflicts strictly by zone priority order: the first zone listed in the
response-policyblock that contains a matching entry for the queried name wins, and all lower-priority zones are ignored for that query. This deterministic first-match behaviour is what makes it possible to layer a local whitelist above a broad threat feed blocklist — the whitelist PASSTHRU entry always takes precedence over any block in a lower-priority zone.
