Set Up Response Policy Zone (RPZ) in BIND Resolver on Debian/Ubuntu

This tutorial will be showing you how to override public DNS records in your BIND DNS resolver with response policy zone (RPZ) on Debian/Ubuntu.

What is Response Policy Zone?

Response policy zone (RPZ) allows a DNS resolver to modify DNS records. It was originally developed as a way to block access to dangerous websites. For example, if a computer queries the IP address of a known dangerous site that spreads malware, the DNS resolver can return 127.0.0.1 as the DNS response, so the computer can’t connect to the dangerous site. This is the original use case. As such, response policy zone is also known as DNS firewall.

You can use RPZ in other ways. For example,

  • If you have self-hosted services like Nextcloud in the local network, you can use RPZ to point your Nextcloud domain (nextcloud.your-domain.com) to the local IP address, so you don’t have to go out to the Internet and then go back to your local network to access the Nextcloud server.
  • Parents can use RPZ to block their children from accessing porn sites.
  • You can block unwanted ads.
  • I install many web applications on my VPS. When the web app isn’t meant for public access, I add DNS record in BIND RPZ only and don’t publish DNS record at my domain registrar to prevent hacking.

Yes, you can create a DNS entry in the /etc/hosts file on the local computer to override public DNS records, but it doesn’t scale well. Furthermore, iOS and Android don’t allow you to create local DNS entries. Wouldn’t it be nice if the BIND DNS resolver overrides the public DNS record, so all devices in the network using the BIND resolver can use the custom DNS record?

Prerequisites

To follow this tutorial, it’s assumed that you have a BIND DNS resolver running on your Debian/Ubuntu server. If not, please read one of the following tutorials to set up BIND resolver.

Once your BIND Resolver is up and running, follow the instructions below.

How to Set Up BIND Response Policy Zone on Debian/Ubuntu Server

First, edit the named.conf.options file.

sudo nano /etc/bind/named.conf.options

Add the following lines in the options {...} clause to enable response policy zone. (The first line is a comment.)

//enable response policy zone. 
response-policy { 
    zone "rpz.local"; 
};

response-policy-zone-bind

Save and close the file. Then open the named.conf.local file.

sudo nano /etc/bind/named.conf.local

Add an RPZ zone in this file.

zone "rpz.local" {
    type master;
    file "/etc/bind/db.rpz.local";
    allow-query { localhost; };
    allow-transfer { 12.34.56.78; };
};

Notes:

  • It is important that you use an absolute path instead of a simple file name in the file directive, or BIND would assume the file is in /var/cache/bind/.
  • RPZ zones should allow queries from localhost only. You don’t need to add local network clients.
  • Replace 12.34.56.78 with the IP address of the slave BIND DNS resolver, which is allowed to do zone transfer. If there’s only one DNS resolver, you can use localhost like this: allow-transfer { localhost; };

Save and close the file. Then we need to create the zone file. Instead of creating a zone file from scratch, we can use a zone template file. Copy the content of db.empty to a new file.

sudo cp /etc/bind/db.empty /etc/bind/db.rpz.local

Then edit the db.rpz file.

sudo nano /etc/bind/db.rpz.local

There is no need to change the existing content. We just add our custom DNS records. For instance, if you have a Nextcloud server on the local network with an IP address 192.168.0.103, then you add the following DNS record, so Nextcloud clients don’t have to go out to the Internet in order to connect to the Nextcloud server.

nextcloud.your-domain.com      A   192.168.0.103

If you don’t want your children to visit porn sites like pornhub.com, add the following line in this file to block the whole pornhub.com domain.

*.pornhub.com          CNAME  .

If you don’t like to see Google Adsense ads on web pages, you can add the following line to block the doubleclick.net domain, which is used to deliver Adsense Ads.

*.doubleclick.net      CNAME   .

Here are some more ad server domains you can block.

*.pubmatic.com        CNAME .
*.mopub.com           CNAME .
*.eskimi.com          CNAME .
*.adcolony.xyz        CNAME .
*.adsrvr.org          CNAME .
*.adsymptotic.com     CNAME .
*.servedby-buysellads.com CNAME .
srv.buysellads.com    CNAME .
*.powerinboxedge.com  CNAME .
*.defof.com           CNAME .
*.licasd.com          CNAME .
*.liadm.com           CNAME .

To override the MX record for a domain name, add a line like below.

example.com         MX     0    mail.example.com.

Note that all left-hand names must NOT end with a dot and all right-hand names must end with a dot.

response policy zone bind9

If you need load balancing for a hostname, then you create a record with multiple values like below. DNS clients will use the two IP addresses randomly and traffic will be distributed between them.

host.example.com         A              12.34.56.78
                         A              12.34.56.79

Save and close the file. It’s recommended to use a separate log file for RPZ to better analyze the log. To configure, edit the BIND main configuration file.

sudo nano /etc/bind/named.conf

Add the following lines to the file.

logging {
    channel rpzlog {
  	file "/var/log/named/rpz.log" versions unlimited size 100m;
    	print-time yes;
    	print-category yes;
    	print-severity yes;
    	severity info;
    };
    category rpz { rpzlog; };
};

Save and close the file. Then create the /var/log/named/ directory and make bind as the owner.

sudo mkdir /var/log/named/
sudo chown bind:bind /var/log/named/ -R

Next, run the following command to check if there are syntax errors in the main configuration file. A silent output indicates no errors are found.

sudo named-checkconf

Then check the syntax of the RPZ zone files.

sudo named-checkzone rpz /etc/bind/db.rpz.local

If no errors are found, then restart BIND9.

sudo systemctl restart bind9

Now you can run the dig command on the BIND server to see if RPZ is working. For example, query a DNS record of a domain name which is included in the response policy zone.

dig A nextcloud.your-domain.com @127.0.0.1

You should see something like below in the command output, which indicates the DNS response was served from local RPZ.

;; AUTHORITY SECTION:
rpz.local			86400	IN	NS	localhost.

You can also check the BIND9 query log.

sudo tail /var/log/named/rpz.log

You would see something like below, meaning the response was served from local RPZ.

(example.com): rpz QNAME Local-Data rewrite example.com via example.com.rpz.local

Fedora Client Doesn’t Use RPZ?

By default, Fedora doesn’t make use of RPZ. You can use the dig command-line utility to find the IP address of a hostname in the RPZ zone, but if you ping the hostname, it can’t find the IP address.

To solve this problem, you need to change the hosts parameter in the /etc/nsswitch.conf file on the Fedora client.

sudo nano /etc/nsswitch.conf

By default, the hosts parameter is defined as:

hosts:   files myhostname mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns

Change it to:

hosts:   files mdns4_minimal [NOTFOUND=return] dns myhostname mymachines

Save and close the file. RPZ should be working now.

Using RPZ with Forwarders

If you add a fowarders directive like below in the options clause in the /etc/bind/named.conf.options file, then your BIND resolver becomes a forwarder, which will forward DNS requests to an upstream DNS resolver like 8.8.8.8.

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

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        forwarders {
                8.8.8.8;
                8.8.4.4;
        };
        ...
};

Response policy zone works with this forwarder setup. Bind will query the local response policy zone first. If DNS record is not found in the RPZ, then the request will be forwarded to an upstream DNS resolver. You might want to use a forwarder to speed up DNS resolution when your own BIND resolver takes too much time resolving DNS names.

Configure Zone Transfer

If you have another BIND DNS resolver, you can configure it as a slave resolver to automatically receive updates from the master DNS resolver.

First, you need to edit the /etc/bind/named.conf.local file on the master DNS resolver.

sudo nano /etc/bind/named.conf.local

Add the IP address of the slave DNS resolver to the allow-transfer directive.

zone "rpz.local" {
    type master;
    file "/etc/bind/db.rpz.local";
    allow-query { localhost; };
    allow-transfer { 12.34.56.78; };
    also-notify { 12.34.56.78; };
};

If you have multiple slave DNS resolver, then add multiple IP addresses like below.

 allow-transfer { 12.34.56.78; 12.34.56.79; };

The also-notify directive will make the master DNS resolver send a notification message to the slave resolver when the RPZ zone is changed. Save and close the file. Restart BIND for the changes to take effect.

sudo systemctl restart bind9

If there’s a firewall running on the master DNS resolver, you need to allow the slave DNS resolver to connect to port 53. For example, if you use UFW firewall, run the following command.

sudo ufw insert 1 allow in from 12.34.56.78 to any port 53

Next, edit the named.conf.options file on the slave DNS resolver.

sudo nano /etc/bind/named.conf.options

Add the following lines in the options {...} clause to enable response policy zone. (The first line is a comment.)

//enable response policy zone. 
response-policy { 
    zone "rpz.local"; 
};

Save and close the file. Then edit the named.conf.local file.

sudo nano /etc/bind/named.conf.local

Add a slave RPZ zone in this file. Replace 11.22.33.44 with the IP address of the master DNS resolver.

zone "rpz.local" {
    type slave;
    file "db.rpz.local";
    masters { 11.22.33.44;};
    allow-notify { 11.22.33.44; };
    allow-transfer { none; };
    allow-query { localhost; };
};

Save and close the file.

You also need to configure the slave resolver’s firewall to allow the master DNS resolver to send notify messages.

sudo ufw insert 1 allow in from 11.22.33.44 to any port 53

Next, run the following command to check if there are syntax errors in the main configuration file. A silent output indicates no errors are found.

sudo named-checkconf

If no errors are found, then restart BIND9.

sudo systemctl restart bind9

After BIND9 restarts, zone tranfer will start immediately. Check the BIND9 log with the following command.

sudo journalctl -eu bind9

or

sudo journalctl -eu named

You can see messages like below, which indicates the zone transfer is successful.

transfer of 'rpz.local/IN' from xx.xx.xx.xx#53: Transfer status: success
transfer of 'rpz.local/IN' from xx.xx.xx.xx#53: Transfer completed: 1 messages, 34 records, 899 bytes, 0.248 secs (3625 bytes/sec)

Note: Whenever you modify the RPZ zone on the master resolver, you need to update the serial number. Make it bigger, so that slave resolvers know the RPZ zone is changed.

Creating Multiple RPZ Zones

Sometimes you might not want certain DNS records to be transferred to slave resolvers. You can create a separate RPZ zone. Edit the named.conf.options file.

sudo nano /etc/bind/named.conf.options

Add a new RPZ zone.

//enable response policy zone. 
response-policy { 
    zone "rpz.local";
    zone "rpz.local.notransfer"; 
};

Note: If the two RPZ zones have conflict DNS records, then the first entry will take precedence. If you want to reverse the precedence, then switch their position, like below:

//enable response policy zone. 
response-policy { 
    zone "rpz.local.notransfer"; 
    zone "rpz.local";
};

Save and close the file. Then open the named.conf.local file.

sudo nano /etc/bind/named.conf.local

Add a definition for the new zone in this file.

zone "rpz.local.notransfer" {
    type master;
    file "/etc/bind/db.rpz.local.notransfer";
    allow-query { localhost; };
    allow-transfer { localhost; };
};

Save and close the file. Then we need to create the zone file. Instead of creating a zone file from scratch, we can use a zone template file. Copy the content of db.empty to a new file.

sudo cp /etc/bind/db.empty /etc/bind/db.rpz.local.notransfer

Then edit the db.rpz file and add custom DNS records.

sudo nano /etc/bind/db.rpz.local.transfer

Troubleshooting Tips

If the secondary DNS resolver fails to replicate RPZ records from the primary DNS resolver, it’s possible that on the secondary DNS resolver:

  • Firewall rule is wrong.
  • The BIND resolver isn’t running.
  • BIND isn’t listening on the required network interface.

How to Use RPZ with Views in BIND

If you want to make your RPZ accessible only to internal trusted networks, you can use views in BIND to accomplish it. This setup will be compatible with the master-slave zone transfer.

First, edit the named.conf.options file.

sudo nano /etc/bind/named.conf.options

Define an internal network and guest network with the acl directive.

options {
     .....
     .....
}

acl internal {
    10.10.10.0/24;
};

acl guest { 
   10.10.20.0/24;
};

Here the 10.10.10.0/24 network is the internal trusted network.  10.10.20.0/24 is the guest network.  Also, delete the following lines from this file.

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

Save and close the file.

Next, edit the named.conf.local file.

sudo nano /etc/bind/named.conf.local

You need to put the zone definition inside a view clause like below. Note that we need to enable the response policy zone inside the view clause.

view "internal" {
  match-clients { internal; };

  //enable the response policy zone.
  response-policy {
     zone "rpz.local";
  };

  zone "rpz.local" {
    type master;
    file "/etc/bind/db.rpz.local";
    allow-query { localhost; };
    allow-transfer { 12.34.56.78; };
  };

};

Save and close the file. Then edit the named.conf file.

sudo nano /etc/bind/named.conf.default-zones

Put the default zones in the guest view.

view guest {
     match-clients { guest; };
     allow-recursion { any; };

    zone "." {
        type hint;
        file "/usr/share/dns/root.hints";
    };

    zone "localhost" {
        type master;
        file "/etc/bind/db.local";
    };

    .....
    .....
};

Save and close the file. Run the following command to check if there are syntax errors in the configuration file. A silent output indicates no errors are found.

sudo named-checkconf

If no errors are found, then restart BIND9 for the changes to take effect.

sudo systemctl restart bind9

response-policy zone expired

If you find the following error in the slave server’s log (sudo journalctl -eu named), it’s probably because the slave isn’t able to connect to the master DNS server.

zone rpz.local/IN: response-policy zone expired; policies unloaded

Wrapping Up

I hope this tutorial helped you set up response policy zone/DNS firewall on Debian/Ubuntu. As always, if you found this post useful, then subscribe to our free newsletter to get more tips and tricks. Take care 🙂

Rate this tutorial
[Total: 3 Average: 5]

9 Responses to “Set Up Response Policy Zone (RPZ) in BIND Resolver on Debian/Ubuntu

  • Frank Godek
    4 years ago

    Interesting. I was not familiar with RPZ. What I have normally done to is setup internal and external views if I wanted an internal IP address for something that would normally translate to an external domain. The view would be triggered by an ACL so that if the request came from an inside IP address, the response would be from the internal view. Thanks for showing me another way to do this.

  • Verry good tutorial.
    However there is a little mistake (I think).
    RPZ means RESPONSE policy zone.

    BIND query the resolvers BEFORE querying/applying the RPZ rules.

    You can test that by interupting WAN connection of the server (Internet connection)
    Then, perform the same query on a domain handled by your RPZ wich lead to SERVFAIL as a result.

    By checking the log of the RPZ zone you can verify than the query does not even reach the RPZ zone.

    It’s an issue for your use-case (personal cloud) , as the server cannot be reached using dns resolution when WAN connection goes down :/

    My use case.
    * WebProxy reachable from the web using my hostname “mydomain.com”
    * WebProxy reachable from my lan using RPZ to override “mydomain.com” by a cname “proxy.int.mydomain.com”
    (No issues with that when the WAN connection is up)

    NB: “proxy.int.mydomain.com” is resolved to the private address of the server no issues with that, even when the net is down. I have a zone dedicated for resolving the subdomains of “int.mydomain.com”.

    • The only way I’ve foud to make it work is to define a zone to resolve internally my domain name.
      (The original nameservers/zone for my domain are hosted by my registar, not by me)

      192.168.0.245 is the addres of my proxy (proxy.int.mydomain.com)
      dns1.int et dns2.int are my internal dns servers (FQDN dns1.int.mydomain.com)

         $TTL    86400  
         @       IN      SOA     mydomain.be. root.mydomain.com. (
                     2020010101
                          28800
                           7200
                         864000
                          86400 )
                      NS      dns1.int
                      NS      dns2.int
                               A       192.168.0.245
         @        IN      A       192.168.0.245
         *          IN      A       192.168.0.245
      
  • Ken Wright
    2 years ago

    Ni hao, Xiao!

    When I run the sudo named-checkconf command, I get the results below:

    /etc/bind/named.conf.default-zones:2: unknown option ‘view’
    /etc/bind/named.conf:13: unknown option ‘logging’

    I can’t find anything about the ‘view’ option or the ‘logging’ option other than the code snippets you’ve included. Can you tell me what I’m doing wrong?

  • Charles
    2 years ago

    Hi Xiao Guoan,

    Thanks for your article. Followed your postfix and bind9 articles. Great so far!

    For Bind9 and RPZ, I want to have different RPZ mapped to different source IP addresses. I searched the web, and it seems this is not possible at all.

    Basically I want one of my children’s computer won’t have any search engine available to him.

    Should I go for the RPZ approach, or some other approach is better?

    • Xiao Guoan (Admin)
      2 years ago

      Yes, it’s possible with BIND. You can use Views in BIND to create access control rules (ACL) for different client IP addresses, and I have already explained how to use views with RPZ in BIND at the end of this article.

      A simpler way is just to create custom DNS entries on your children’s computer.

      • Charles
        2 years ago

        Oh, yes. Custom DNS on his computer will fix the issue! Simple and effective!

        Duh, I always over-engineer solutions… ha ha. Much thanks!

  • Charles
    2 years ago

    Hi Xiao Guoan,

    It seems that we can only use FQDN, not wildcard DN.

    www.iwanttodeliver.com          CNAME .         <- seems to work after certain time period
    *.iwanttodeliver.com            CNAME .         <- doesn't seem to work with browsers
    

    dig seems to work though…

    dig A abc.stripchat.com
    
    ; <> DiG 9.16.15-Ubuntu <> A abc.stripchat.com
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 29997
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 2
    
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 1232
    ; COOKIE: 0e8c7a7c87993e9a01000000625ce0326bcd4518e5fd1452 (good)
    ;; QUESTION SECTION:
    ;abc.stripchat.com.		IN	A
    
    ;; ADDITIONAL SECTION:
    rpz.local.		1	IN	SOA	localhost. root.localhost. 1 604800 86400 2419200 86400
    
    ;; Query time: 407 msec
    ;; SERVER: 127.0.0.1#53(127.0.0.1)
    ;; WHEN: Mon Apr 18 11:51:14 HKT 2022
    ;; MSG SIZE  rcvd: 133
    
    

    syslog and rpz.log don't have anything when I do "dig".

    What else can I check to see why wildcard doesn't work?

  • To avoid problems with resolving root servers I had to add ‘forward only;’ to named.conf.options file.

    Check realtime logging: sudo tail -f /var/log/syslog

    I got the following error messages:
    ‘resolver priming query failure’
    ‘unexpected RCODE (REFUSED) resolving ‘

    NAMED.CONF.OPTIONS:
    acl “trusted” { 10.0.0.0/8; localhost; };

    options {
    directory “/var/cache/bind”;
    forwarders { 103.86.96.100; 103.86.99.100; };
    forward only;
    recursion yes;
    allow-recursion { trusted; };
    allow-query { trusted; };
    dnssec-validation yes;
    auth-nxdomain no;
    listen-on { 10.1.1.100; };
    allow-transfer { none; };
    querylog yes;
    response-policy {
    zone “rpz.local”;
    };
    };

    NAMED.CONF.LOCAL:
    zone “rpz.local” {
    type primary;
    file “/etc/bind/db.rpz.local”;
    allow-transfer { 10.1.1.101; 10.2.0.100; };
    also-notify { 10.1.1.101; 10.2.0.100; };
    };

    zone “1.10.in-addr.arpa” {
    type primary;
    file “/etc/bind/db.10.1”;
    allow-transfer { 10.1.1.101; 10.2.0.100; };
    };

    zone “2.10.in-addr.arpa” {
    type primary;
    file “/etc/bind/db.10.2”;
    allow-transfer { 10.1.1.101; 10.2.0.100; };
    };

    NAMED.CONF:
    include “/etc/bind/named.conf.options”;
    include “/etc/bind/named.conf.local”;
    include “/etc/bind/named.conf.default-zones”;

    logging {
    channel rpzlog {
    file “/var/log/named/rpz.log” versions unlimited size 100m;
    print-time yes;
    print-category yes;
    print-severity yes;
    severity info;
    };
    category rpz { rpzlog; };
    };

    Thanks for the instructions, it helped me a lot.

Leave a Comment

  • Comments with links are moderated by admin before published.
  • Your email address will not be published.
  • Use <pre> ... </pre> HTML tag to quote the output from your terminal/console.
  • Please use the community (https://community.linuxbabe.com) for questions unrelated to this article.
  • I don't have time to answer every question. Making a donation would incentivize me to spend more time answering questions.

The maximum upload file size: 2 MB. You can upload: image. Links to YouTube, Facebook, Twitter and other services inserted in the comment text will be automatically embedded. Drop file here