Response Policy Zones (RPZ) let BIND rewrite DNS responses that match policy rules: blocked, dropped, redirected, or passed through with an exception marker. The RPZRewrites counter in BIND’s Name Server Statistics tracks how often this happens. When that counter spikes, it is usually the first telemetry signal that something inside your network is reaching known-bad infrastructure.

This article covers what RPZRewrites actually counts, what it silently excludes, how to distinguish a real malware outbreak from background noise, and what RPZ costs in query performance and memory. It assumes RPZ is already configured. For the broader BIND monitoring framework, see How BIND actually works in production: a mental model for operators.

What RPZRewrites counts and what it excludes

The RPZRewrites counter lives in NSStats alongside the query analysis counters. It aggregates across all configured RPZ zones. Retrieve it from the statistics channel or rndc stats:

# Current RPZRewrites value via statistics channel
curl -s http://localhost:8653/json/v1/server | \
  python3 -c "import sys,json; d=json.load(sys.stdin); \
  print('RPZRewrites:', d.get('nsstats',{}).get('RPZRewrites',0))"

# Or via rndc stats (path depends on your statistics-file setting)
rndc stats && grep RPZRewrites /var/named/data/named_stats.txt

The counter is cumulative since process start, like all NSStats counters. To get a rate, sample twice and compute the delta.

The critical detail: RPZRewrites only counts rewrites that change the response. PASSTHRU actions, which explicitly allow a query through despite matching an RPZ rule, are excluded from the global aggregate. Per-RPZ-zone statistics exist internally and track both enabled and disabled rewrites, but the statistics channel exposes only the aggregate non-PASSTHRU count. Per-zone breakdowns are not available through the standard JSON/XML endpoint.

This exclusion matters operationally. If your RPZ policy uses PASSTHRU to whitelist specific subnets or domains that would otherwise be blocked by a broader rule, those whitelist hits are invisible in RPZRewrites. You cannot tell how often your exceptions are firing.

flowchart TD
    Q[Client query arrives] --> L{Cache or zone lookup}
    L --> R{RPZ rule match?}
    R -->|No match| N[Normal response sent]
    R -->|PASSTHRU match| P[Response allowed through
NOT counted in RPZRewrites] R -->|Block or redirect action| B[Response rewritten
RPZRewrites incremented] B --> B1[NXDOMAIN / NODATA / DROP / TCP-ONLY / Local Data]

How RPZ interception works in the query pipeline

RPZ rules are evaluated after cache lookup and zone lookup but before the response is serialized. Every query passes through the RPZ evaluation layer if RPZ is configured. BIND supports five trigger types:

  • QNAME: matches the queried domain name itself. The most common trigger type and what most threat intelligence feeds deliver.
  • CLIENT-IP: matches the source IP of the querying client. Useful for applying different policies to different network segments.
  • IP: matches IP addresses in the answer section (A or AAAA records returned by the upstream).
  • NSDNAME: matches the nameserver names in the delegation chain.
  • NSIP: matches the IP addresses of nameservers in the delegation chain.

When a trigger matches, BIND applies the policy action associated with that rule:

ActionWhat the client receivesCounted in RPZRewrites
NXDOMAINNXDOMAIN responseYes
NODATANOERROR with empty answerYes
PASSTHRUNormal response (whitelist exception)No
DROPNo response at allYes
TCP-ONLYTruncated response, forcing TCP retryYes
Local DataSubstituted RR (e.g., walled-garden redirect)Yes

A single response-policy statement can contain up to 64 policy zones. Each zone is evaluated independently per query, and the most restrictive matching action wins. RPZ zones can be loaded from local files or received via zone transfers from threat intelligence providers.

Reading a spike: malware outbreak or noise?

Under normal conditions, RPZRewrites reflects your baseline threat landscape. A recursive resolver serving a corporate network sees a steady trickle of rewrites as clients hit known-bad domains through browser redirects, ad networks, or cached links. This background rate is your baseline.

A spike exceeding 10x baseline usually indicates malware or botnet activity inside the network. A sustained high rate suggests an active, ongoing infection rather than a transient event.

Distinguishing an outbreak from noise requires correlation across several signals:

Check magnitude and duration. A brief spike lasting minutes could be a single compromised host running a quick callback. Sustained elevation over hours means the threat is persistent and likely spreading or actively communicating.

Correlate with NXDOMAIN rate. Some RPZ actions return NXDOMAIN. If your NXDOMAIN counter rises in lockstep with RPZRewrites, the increase is from RPZ enforcement, not from a random subdomain attack or broken delegation. This is a common source of misdiagnosis: operators see NXDOMAIN spiking and assume attack, when RPZ is doing its job.

Identify which domains are being intercepted. The rpz logging category captures RPZ rewrites at info severity. BIND 9.18 reportedly added a separate rpz-passthru category for PASSTHRU activity. Check your RPZ logs:

# RPZ rewrite log entries (category: rpz)
journalctl -u named --since "1 hour ago" | grep -i "rpz"

One limitation: even at info severity, RPZ logs record the owner-name (the trigger) but not the substituted RR or target-name. If your RPZ action redirects to a walled garden, the logs show what was blocked but not where it was redirected.

Check source IP concentration. If the spike comes from a single subnet or a small set of client IPs, you likely have a localized infection. If it is distributed across many clients, it could indicate broader compromise or a new threat intelligence feed entry catching previously-unknown traffic. Source IPs appear in the RPZ log entries; the statistics channel does not provide per-client breakdowns.

Cross-reference with QType distribution. Botnet callbacks and malware C2 channels often generate specific query patterns: frequent TXT queries (DNS tunneling), repeated A queries for fast-flux domains, or periodic queries to dynamically generated names. Check the QType distribution:

# Top query types
curl -s http://localhost:8653/json/v1/server | \
  python3 -c "import sys,json; d=json.load(sys.stdin); \
  print(dict(sorted(d.get('qtypes',{}).items(), key=lambda x:-x[1])[:10]))"

Cross-reference with security tooling. RPZRewrites is a network-level signal. Correlate with endpoint detection and response (EDR) alerts, firewall logs, and proxy logs for the same time window. A spike in RPZ rewrites should map to specific hosts that your security tools can investigate further.

The cost of running RPZ

RPZ is not free. Each configured RPZ zone adds 1 to 4 additional database lookups per query, depending on which trigger types are active.

The performance impact is significant: a single RPZ zone with QNAME and IP triggers can reduce maximum queries-per-second by approximately 20%. Four RPZ zones can reduce throughput by approximately 50%. This overhead applies to every query, not just the ones that match RPZ rules. The evaluation happens before the match decision, so even queries that pass through unmodified pay the lookup cost.

Memory consumption scales with blocklist size. Large RPZ datasets with millions of entries consume substantial memory, comparable to loading additional zones. Factor this into memory planning alongside cache and zone data. The max-cache-size setting bounds the cache, but RPZ memory is separate and not subject to that limit. On a resolver with large blocklists, total BIND memory can significantly exceed max-cache-size plus zone overhead.

On BIND 9.20, RPZ zone loading was moved to libuv threadpools, improving latency when RPZ updates interleave with normal query processing. A known issue (GL #4898) can cause long-running tasks in offloaded threads, including RPZ zone loading, to block query resolution and cause timeouts. The workaround is to set the UV_THREADPOOL_SIZE environment variable to the number of RPZ zones plus the number of worker threads. If you are running 9.20 with many RPZ zones and seeing unexplained query timeouts during feed updates, check this first.

Operational tradeoffs

DNSSEC interaction. By default, RPZ does not apply to DNSSEC-signed responses (break-dnssec no). Setting break-dnssec yes applies RPZ to signed responses but strips DNSSEC records from the rewritten response. Validating client resolvers may then treat the response as bogus. If you need RPZ enforcement on signed domains, you are trading DNSSEC integrity for policy enforcement.

Zone name leakage. NXDOMAIN and NODATA RPZ responses include the RPZ zone’s SOA record in the Authority section, just like any other NXDOMAIN response. If your RPZ zone has an obvious name (for example, rpz-blocklist or malware-rpz), clients can detect that RPZ is active and which blocklist is in use. Use a non-obvious zone name and avoid localhost as the NS target.

False positives. Misconfigured RPZ rules or overly broad blocklist entries can block legitimate sites. Because some RPZ actions return NXDOMAIN, a false positive looks identical to a genuinely non-existent domain from the client’s perspective. Users will report “the website is down” rather than “DNS is blocking this site.” Watch for support tickets that correlate with RPZRewrites changes after feed updates.

No per-zone visibility via statistics channel. The global RPZRewrites counter aggregates across all zones. If you run multiple RPZ feeds and one generates most rewrites, the counter alone cannot tell you which. Per-RPZ-zone statistics exist internally but are not exposed through the JSON/XML statistics endpoint. For per-zone visibility, rely on RPZ logging.

No RPZ-specific rndc commands. Standard zone management commands (rndc reload, rndc zonestatus, rndc retransfer) apply to RPZ zones like any other zone. There is no rndc rpz subcommand. To force an RPZ feed update, use rndc retransfer <zone-name>.

Signals to watch in production

SignalWhy it mattersWarning sign
RPZRewrites ratePrimary threat-interception indicatorSpike greater than 10x baseline
RPZRewrites sustained rateDistinguishes transient event from active infectionElevated rate persisting for hours
NXDOMAIN rateRPZ NXDOMAIN actions inflate this counterNXDOMAIN rising in correlation with RPZRewrites
QType distributionMalware callbacks generate specific patternsTXT spikes (tunneling), repetitive A queries (fast-flux)
Process RSSLarge RPZ datasets consume memory beyond cache limitsRSS growth unexplained by cache or zone data
Query latencyRPZ adds per-query lookup overheadp95 latency increase after adding RPZ zones
BIND 9.20 threadpool timeoutsRPZ loading can block query resolutionUnexplained SERVFAIL or timeouts during feed updates

How Netdata helps

  • Netdata collects RPZRewrites per-second from the BIND statistics channel. A spike starting at 14:03:17 is visible at 14:03:17, not at the next 5-minute polling interval.
  • Anomaly detection learns your baseline RPZ rewrite rate, including diurnal patterns, and flags deviations without manual threshold tuning. Baseline rates vary enormously between networks depending on blocklist coverage and client behavior.
  • Correlating RPZRewrites with NXDOMAIN rate, QType distribution, and per-client query patterns in the same dashboard distinguishes “RPZ is blocking something new” from “clients are misconfigured” without switching tools.
  • Memory metrics for the named process track RPZ blocklist memory impact alongside cache usage, which matters when feeds grow or new zones are added.
  • On BIND 9.20, correlating RPZ feed update events with query latency or SERVFAIL spikes helps identify threadpool blocking before users notice.