Block Malware at the DNS Level with BIND9 + RPZ

DNS BIND9 RPZ

Your firewall blocks IPs. Your IDS inspects packets. But most malware calls home using domain names, not raw IPs. A DNS-level block kills the connection before it even starts — the client resolves the malicious domain, gets NXDOMAIN, and the payload never downloads.

This is what Response Policy Zones (RPZ) do in BIND9. This guide shows you how to set it up from scratch on Debian/Ubuntu, with automated threat feed updates.

What is RPZ?

RPZ is a BIND9 feature that lets you override DNS responses for specific domains. When a client queries a domain on your blocklist, BIND returns NXDOMAIN (or a redirect) instead of the real IP.

Think of it as a DNS-level firewall:

RPZ works transparently — no client configuration needed. If the machine uses your DNS server, it's protected.

Manual setup

Install BIND9

apt install bind9 bind9-utils

Create the RPZ zone file

$TTL 300
@ IN SOA localhost. admin.localhost. (
    2025010101 3600 600 604800 300
)
@ IN NS localhost.

; Block known malware domains
malware-domain.com    CNAME .
evil-tracker.net      CNAME .
phishing-bank.com     CNAME .

; Whitelist (passthru) — override blocklist false positives
good-domain.com       CNAME rpz-passthru.

CNAME . means NXDOMAIN — the domain ceases to exist for your clients.

Configure BIND to use RPZ

Add to /etc/bind/named.conf.options:

options {
    directory "/var/cache/bind";

    forwarders {
        1.1.1.1;
        9.9.9.9;
    };

    dnssec-validation auto;
    listen-on { any; };
    allow-query { localhost; 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; };

    response-policy {
        zone "rpz.local" policy given;
    };
};

Test it

systemctl restart bind9
dig @127.0.0.1 malware-domain.com

Expected: NXDOMAIN. The domain is blocked.

The hard part: threat feeds

A static blocklist is useless in a week. You need automated feeds that update daily. The good free ones:

FeedWhat it blocksLicense
abuse.ch URLhausMalware distribution URLsCC BY
Hagezi MultiAds, tracking, malwareMIT
OISD BigComprehensive ad/malware listPublic

Converting feeds to RPZ format

Most feeds come as domain lists. You need to convert them to RPZ zone format, handle serial numbers, reload BIND, manage whitelists for false positives. This gets complex fast.

#!/bin/bash
# update-rpz.sh — download feeds and rebuild RPZ zone

curl -sL "https://urlhaus.abuse.ch/downloads/rpz/" -o /tmp/urlhaus.rpz
curl -sL "https://big.oisd.nl/domainswild2" -o /tmp/oisd.txt

# Build zone header
cat > /etc/bind/db.rpz.feeds << EOF
\$TTL 300
@ IN SOA localhost. admin.localhost. (
    $(date +%Y%m%d%H) 3600 600 604800 300
)
@ IN NS localhost.
EOF

# Append feeds, convert format, deduplicate...
# Then reload BIND
rndc reload

Managing whitelists

Feeds will have false positives. When a legitimate domain gets blocked, you need to add it to a whitelist zone that takes priority:

response-policy {
    zone "rpz.whitelist" policy passthru;  // whitelist first!
    zone "rpz.local" policy given;          // custom blocks
    zone "rpz.feeds" policy given;          // automated feeds
};

Order matters: whitelist overrides everything.

The problem with manual RPZ

Setting this up correctly takes time:

The automated way: NetForge DNS Sicuro

NetForge generates the complete BIND9 + RPZ configuration in one click:

  1. Select your threat feeds (URLhaus, Hagezi, OISD)
  2. Add custom blacklist/whitelist domains
  3. Configure forwarders (Cloudflare, Quad9)
  4. Enable DDNS for VPN integration (optional)
  5. Click Generate

You get:

Everything is generated as downloadable files — copy to your server and run. No agent, no daemon, no phone-home.

VPN + DNS integration

If you run OpenVPN or WireGuard, NetForge generates the event hooks that register VPN clients in your DNS zone automatically. VPN clients get DNS names, blocked by the same RPZ feeds, logged in the same BIND query log.

Monitoring what RPZ blocks

BIND logs RPZ actions in the query log. Parse it to see what your DNS is blocking:

grep "rpz" /var/log/named/query.log \
  | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20

Try NetForge

Complete BIND9 + RPZ configuration in one click. Threat feeds, whitelists, VPN integration.

Live Demo Get NetForge