Small-Time DNS with BIND9

This document shows how to configure a basic installation of the ISC DNS server, BIND9, for Alpine Linux. This is useful when you want to have a DNS server for your home or home office network. The instructions start with a basic caching, forwarding DNS server. It continues on with adding lookup zones for your LAN hosts. Finally, it gives a peek at setting up ad blocking. You can work through as much or as little as you like depending on your DNS needs.
It should be noted, this document focuses on quick deployment and ease of use for a trusted network behind a firewall. Do not deploy this configuration on a host that allows DNS queries from the internet.
== Install the Package ==
The usual Alpine apk installation method can be used to install bind9. This will install the named DNS server as well as nslookup and other utilities.
 # apk update
 # apk add bind
== Configure as a Forwarding DNS Server ==
When configured as a forwarding server, the local DNS server will cache the results of query replies. Queries not found in cache are forwarded to a public DNS server (or your ISP).
The example below uses public DNS servers 1.1.1.1 and 8.8.8.8. You may substitute your ISP’s DNS servers or any other trusted DNS server.
 # cd /etc/bind
 
 # cat << EOF > named.conf
 
 options {
 directory "/var/bind";
 
 listen-on { any; };
 listen-on-v6 { none; };
 
 dnssec-validation no;
 
 forwarders {
 1.1.1.1;
 8.8.8.8;
 };
 
 recursion yes;
 };
 EOF
After writing the configuration, run named-checkconf to verify it’s correct. Fix any errors before moving on.
=== Start the DNS Server ===
 # service named start
 # rc-update add named
=== Test Queries Locally ===
First, run nslookup on your Alpine host and set the server to your Alpine host’s IP address. Then, query some internet DNS name and IP addresses. You should see IP addresses and domain names in the reply.
If you see an error, like ** server can’t find example.com: NXDOMAIN, check the configuration in named.conf.
Here’s an example of a successful test.
 $ nslookup
 > server 192.168.1.100
 Default server: 192.168.1.100
 Address: 192.168.1.100#53
 
 > example.com
 Server: 192.168.1.100
 Address: 192.168.1.100#53
 
 Non-authoritative answer:
 Name: example.com
 Address: 23.215.0.136
 
 > 1.1.1.1
 1.1.1.1.in-addr.arpa name = one.one.one.one.
Note: Output has been truncated for brevity.
If the test is successful, you’ll want to configure your host’s /etc/resolv.conf to use its own IP address for DNS lookups.
=== Test Queries from a LAN Client ===
Repeat the test, but from another machine on your network. Be sure to use the Alpine host’s IP address for the nslookup server.
If you see an error like *** [192.168.1.100] can’t find example.com: Query refused, check your named.conf. Verify there is a line for recursion yes;
Here’s a sample of a successful test on a Windows client.
 C:\> nslookup
 > server 192.168.1.100
 
 > example.com
 Server: [192.168.1.100]
 Address: 192.168.1.100
 
 Non-authoritative answer:
 Name: example.com
 Addresses: 2600:1406:bc00:53::b81e:94ce
 23.215.0.136
Note: Output has been truncated for brevity.
If the test is successful, you can configure your DHCP server to use the Alpine host’s IP address for primary DNS.
== Optionally Add LAN Hosts ==
So far, only well-known internet hosts are able to be queried. Sometimes it’s useful to have hosts on your own network be available in DNS as well. This requires setting up a zone file.
Creatig a zone file is a little more complex than the configuration thus far, but not insurmountable. There are many examples of zone files to be found on the internet, including the BIND9 documentation, a Wikipedia article, and another Alpine Wiki DNS howto. All these can be used as references.
=== Create the Zone File ===
The example below is for a simple home network using the reserved private top-level domain name of .home. The top half of the file contains details about the domain itself. The bottom half is where individual hosts and their addresses are listed. You will need to adjust names and IP addresses for your network configuration.
 # mkdir /etc/bind/zones
 
 # chown named /etc/bind/zones
 
 # cat << EOF > /etc/bind/zones/home
 
 $TTL 3600
 @ IN SOA ns1.home. root.home. (
 1 ; Serial - increment after modifying
 604800 ; Refresh
 86400 ; Retry
 2419200 ; Expire
 604800 ) ; Negative Cache TTL
 ;
 
 @ IN NS ns1
 @ IN A 192.168.1.1
 router IN A 192.168.1.1
 ns1 IN A 192.168.1.100
 alpine IN A 192.168.1.100
 media IN A 192.168.1.101
 EOF
Be sure to check the file using named-checkzone and fix any errors before continuing. The positional parameters are zone name and file name.
 # named-checkzone home /etc/bind/zone/home
 zone home/IN: loaded serial 1
 OK
If you do encounter errors in the output, there is a line number included that will help you track it down.
=== Reference the Zone File in named.conf ===
Edit /etc/bind/named.conf and append the following lines:
 zone "home" {
 type primary;
 file "/etc/bind/zones/home";
 };
Validate the configuration with the command: named-checkconf. Fix any errors before continuing.
=== Inform the DNS Server of the New Configuration ===
Use the usual Alpine service command to reload the configuration.
 service named reload
If there are errors, use the named-checkconf and named-checkzone commands to help narrow down the cause.
=== Do Some Test Queries ===
As a final check, revisit the tests using nslookup that were used to verify the forwarding DNS server. This time, in addition to looking up internet hosts, try some queries for the hosts in your zone file.
Here’s an example of a successful test.
 $ nslookup
 > server 192.168.1.100
 Default server: 192.168.1.100
 Address: 192.168.1.100#53
 
 > example.com
 Server: 192.168.1.100
 Address: 192.168.1.100#53
 
 Non-authoritative answer:
 Name: example.com
 Address: 23.215.0.136
 
 > router.home
 Server: 192.168.1.100
 Address: 192.168.1.100#53
 
 Name: router.home
 Address: 192.168.1.1
Note: Output has been truncated for brevity.
== Add Zones for localhost ==
To make the configuration complete, there should be a zone for the localhost name so the DNS server will return the familiar 127.0.0.1 address when queried. The DNS server should also respond with localhost when queried for the IP address 127.0.0.1
The process for creating the forward lookup zone is the same as is was for creating the home zone. What’s new in this step is the concept of reverse lookup zones. This is simply the DNS server returning a name when asked about an IP address.
Here are the steps:
# Create the zone file
# Reference the zone in named.conf
# Reload the configuration
Don’t forget to test with named-checkzone, named-checkconf, and nslookup as you go.
=== Create the Forward Lookup Zone File ===
The zone file for localhost is fairly simple, with only one A record. An example is show below. Create a new file with the path /etc/bind/zones/localhost and copy the contents of the example into it.
 $TTL 3600
 @ IN SOA localhost. root.localhost. (
 1 ; Serial - increment after modifying
 604800 ; Refresh
 86400 ; Retry
 2419200 ; Expire
 604800 ) ; Negative Cache TTL
 ;
 
 @ IN NS localhost.
 @ IN A 127.0.0.1
=== Create the Reverse Lookup Zone File ===
The reverse lookup zone file is nearly the same as the forward lookup, but has a PTR record instead of an A record on the last line. Create a new file with the path /etc/bind/zones/1.0.0.127.in-addr.arpa and copy the contents from below.
 $TTL 3600
 @ IN SOA localhost. root.localhost. (
 1 ; Serial - increment after modifying
 604800 ; Refresh
 86400 ; Retry
 2419200 ; Expire
 604800 ) ; Negative Cache TTL
 ;
 
 @ IN NS localhost.
 1 IN PTR localhost. 
The rather strange looking naming convention of the file (and the zone itself) is simply the IP address of the reverse zone with its octets in reverse order and the suffix .in-addr.arpa tacked on the end. You can actually name it whatever you want provided it matches the configuration in named.conf. Some examples will use localhost.rev for the reverse lookup filename.
=== Reference the Zone Files in named.conf ===
After checking the new zone files with named-checkzone, append a reference to them in the named.conf file. The configuration to be added is shown below.
 zone "localhost" {
 type primary;
 file "/etc/bind/zones/localhost";
 };
 
 zone "1.0.0.127.in-addr.arpa" {
 type primary;
 file "/etc/bind/zones/1.0.0.127.in-addr.arpa";
 };
If named-checkconf does not raise any errors, the next step is to reload the configuration with service named reload
== Reverse Lookup Zone for LAN Hosts ==
If you created a zone file for hosts on your local network, you should create a reverse lookup zone for completeness. The process is very similar to the creation of the localhost reverse lookup zone. A shortcut method is to copy the forward lookup zone file to a new filename of the IP of your LAN and then replace the A records with their corresponding PTR records.
Following the example given in the forward lookup, the reverse zone would look like what’s shown below.
 $TTL 3600
 @ IN SOA ns1.home. root.home. (
 1 ; Serial - increment after modifying
 604800 ; Refresh
 86400 ; Retry
 2419200 ; Expire
 604800 ) ; Negative Cache TTL
 ;
 
 @ IN NS ns1
 @ IN A 192.168.1.1
 router IN A 192.168.1.1
 ns1 IN A 192.168.1.100
 alpine IN A 192.168.1.100
 media IN A 192.168.1.101
=== On-Going Maintenance of LAN Hosts ===
When using DNS for your own network’s hosts, there are a few steps to keep in mind when you add or remove hosts.
# Increment the serial number on the zone file.
# Test the zone file with named-checkzone
# Test the configuration with named-checkconf
# Reload the named service to make the changes active.
== Blocking Ads ==
The Response Policy Zone (RPZ) feature of BIND9 makes it easy to block access to an unwanted domain by returning a domain not found response instead of an IP address. Three steps are required to get this working.
# Enable to RPZ feature in the options section of named.conf
# Create a zone file lising unwanted domains.
# Reference the zone file in named.conf
=== Enable RPZ in named.conf options ===
Below is the options section of named.conf used in all the examples so far, but with the addition of response policy zones. New lines for RPZ appear following the recursion yes; directive. The remainder of named.conf has been omitted for brevity.
 options {
 directory "/var/bind";
 
 listen-on { any; };
 listen-on-v6 { none; };
 
 dnssec-validation no;
 
 forwarders {
 1.1.1.1;
 8.8.8.8;
 };
 
 recursion yes;
 
 response-policy {
 zone "rpz";
 };
 };
After making the changes, run named-checkconf to ensure there are no problems with the file.
=== Create an RPZ File ===
The RPZ zone file looks a lot like a regular zone file. One way to create it quickly is to start by copying the localhost zone file and then appending the hosts to be blocked.
For example:
 # cd /etc/bind/zones
 
 # cp localhost rpz
 
 # cat << EOF >> rpz
 example.com CNAME .
 *.example.com CNAME .
 EOF
The CNAME . record instructs BIND9 to return an NXDOMAIN (domain not found) response when the listed domain is queried. You probably wont want to block example.com, but it works well for demonstration purposes.
Check the file for errors by running named-checkzone rpz /etc/bind/zone/rpz and fix any errors before moving on to the next step.
=== Reference the RPZ file in named.conf ===
Just like the instructions for adding localhost and LAN host zones, adding the RPZ zone file involves appending a few lines to named.conf and running named-checkconf before reloading the service.
The format for a new zone should be familiar by now. It looks like what’s shown below.
 zone "rpz" {
 type primary;
 file "/etc/bind/zones/rpz";
 };
After appending this to named.conf, be sure to run named-checkconf. Then reload the configuration with service named reload
=== Test RPZ Blocking ===
To test, use nslookup just like before when testing the forwarding configuration, LAN hosts, etc. Except this time, you’re hoping to see an error message.
Below an example showing the successful configuration to block example.com.
 $ nslookup
 > example.com
 Server: 192.168.1.100
 Address: 192.168.1.100#53
 
 ** server can't find example.com: NXDOMAIN
Continue testing with some other domains that should be allowed just to make sure it’s being blocked by the RPZ policy and not because the server is misconfigured.
=== Maintaining the RPZ File ===
Manually adding blocked domains to the RPZ zone file can quickly become tedious, but with this simple test involving example.com, you can see how it’s done. There are some folks on the internet who curate and maintain blocklists. One such source is https://github.com/hagezi/dns-blocklists
To use an ad blocking RPZ you’ll want to perform the following steps:
# Download the file from the source location.
# Check the file’s validity with named-checkzone
# Replace the existing /etc/bind/zones/rpz file with the downloaded one.
# Reload the configuration by running service named reload
New ad domains pop up all the time. The trick is automating the download of new block lists when they come out. A shell script like the one below can help.
 #! /bin/sh
 
 cd /etc/bind/zones || exit 1
 curl -Os https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/multi.txt || exit 2
 named-checkzone rpz ./multi.txt || exit 3
 cp rpz rpz~
 mv multi.txt rpz
 service named reload
== Next Steps ==
This document has taken you through the basics of setting up BIND9 on your Alpine server. There are many more features that haven’t been covered. Some of the interesting ones include:
* Wildcard subdomains
* IPv6 domains
* Secondary DNS servers
* DNSSEC
Whichever you choose to pursue is up to you. See the BIND9 documentation for more information.
Category:Networking