Block Malware at the DNS Level with 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:
- Blocklists: malware C2 servers, phishing domains, ad networks
- Whitelists: override false positives (passthru)
- Custom entries: block specific domains for your network
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:
| Feed | What it blocks | License |
|---|---|---|
| abuse.ch URLhaus | Malware distribution URLs | CC BY |
| Hagezi Multi | Ads, tracking, malware | MIT |
| OISD Big | Comprehensive ad/malware list | Public |
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:
- Zone file formatting is fragile (missing serial number = silent failure)
- Feed URLs change, feeds go offline, formats differ
- Whitelist management is manual
- No visibility into what's being blocked
- DNSSEC + RPZ interaction needs careful configuration
- Adding a VPN? You need DDNS integration with proper TSIG keys
The automated way: NetForge DNS Sicuro
NetForge generates the complete BIND9 + RPZ configuration in one click:
- Select your threat feeds (URLhaus, Hagezi, OISD)
- Add custom blacklist/whitelist domains
- Configure forwarders (Cloudflare, Quad9)
- Enable DDNS for VPN integration (optional)
- Click Generate
You get:
- Complete
named.confwith RPZ zones - Zone files for whitelist, custom blocks, and feeds
update-rpz.shscript with cron entrysetup-bind.shinstaller script- TSIG key for secure DDNS updates
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