Create a DNS server on Debian
Published on 17 min read
Updated on
In this series40 min read in total
A DNS server translates names into addresses. Rather than remembering that the NAS sits at 172.16.30.30, you type nas.it.fr and the server does the mapping. This lab builds that service end to end with Bind9 on Debian: a machine that answers for its own domain, in both directions, and relays to the outside whatever it does not know.
What a DNS server does and what we are building
A resolution happens in three moves. The client queries the DNS server it was pointed at. If the requested name belongs to a zone this server holds the reference copy of, it answers directly and is then authoritative. Otherwise it passes the question to another server, called a forwarder, and relays its answer back.
Our server will play both roles: authoritative for it.fr, forwarder to the outside for everything else.
Here is this lab’s configuration. These values come back in every file, replace them with yours throughout.
| Setting | Value | Where it reappears |
|---|---|---|
| Server address | 172.16.10.10 | /etc/hosts, resolv.conf, zones |
| Subnet mask | 255.255.0.0, that is /16 | network interface, reverse zone name |
| Machine name | dns | /etc/hostname, /etc/hosts, zones |
| Domain name | it.fr | everywhere |
| Forward zone | it.fr in /etc/bind/db.it.fr | named.conf.local |
| Reverse zone | 16.172.in-addr.arpa in /etc/bind/db.it.fr.inv | named.conf.local |
Three machines will be declared in the zone:
| Full name | Address | Role |
|---|---|---|
dns.it.fr | 172.16.10.10 | the DNS server itself |
client.it.fr | 172.16.20.20 | a client workstation |
nas.it.fr | 172.16.30.30 | a file server |
A DNS server advertises its own address to clients. That address must therefore be known and stable before anything is installed.
Step 1: pin down the machine’s network identity
Name the machine
Terminal window sudo nano /etc/hostname/etc/hostname dnsThis file holds the short name only, without the domain. The full name
dns.it.fris assembled by/etc/hoststwo steps further down.Check the interface address
Terminal window ip aip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000link/ether 00:0c:29:cd:01:1a brd ff:ff:ff:ff:ff:ffaltname enp11s0inet 172.16.10.10/16 brd 172.16.255.255 scope global ens192valid_lft forever preferred_lft foreverinet6 fe80::20c:29ff:fecd:11a/64 scope linkvalid_lft forever preferred_lft foreverInterface
ens192does carry172.16.10.10/16. If you already fixed the address during the Debian installation, move on to the next step.Switch the interface to static if needed
Declare the full name in /etc/hosts
Terminal window sudo nano /etc/hosts/etc/hosts 172.16.10.10 dns.it.fr dns127.0.0.1 dnsThe syntax is
DNS_SERVER_IP<tab>HOSTNAME.DOMAIN<tab>HOSTNAME.Point at the resolver to use
Terminal window sudo nano /etc/resolv.conf/etc/resolv.conf domain it.frsearch it.frnameserver 172.16.10.10The
search it.frline lets you typenasinstead ofnas.it.fr: the suffix is appended automatically. Thenameserverline designates the server to query, here the machine itself.
The machine has a fixed address, a full name, and knows who to query. The service can now be installed.
Step 2: install Bind9
sudo apt update && sudo apt install bind9 dnsutilsapt update refreshes the package list from sources.list. bind9 is the DNS server proper, dnsutils provides the dig and nslookup diagnostic tools, indispensable for the final verification.
The installation drops several files into /etc/bind. Knowing which does what saves a lot of guesswork:
| File | Role | Do we touch it? |
|---|---|---|
named.conf | simply includes the three files below | no |
named.conf.options | global behaviour: cache, forwarders, DNSSEC | yes, step 4 |
named.conf.local | declaration of the zones this server is responsible for | yes, step 4 |
named.conf.default-zones | technical zones (localhost, root) | no |
db.local, db.127 | shipped zone templates, meant to be copied | we copy them |
db.<domain> | the actual content of a zone, created by you | yes, step 3 |
The configuration files are there, but none of them describes it.fr yet.
Step 3: write the zone files
A zone is a file listing the records this server is authoritative for. Two are needed: one to translate names into addresses, one for the opposite.
The forward zone
Copy the shipped template
Terminal window sudo cp /etc/bind/db.local /etc/bind/db.it.frdb.localalready holds a validSOAandNSrecord: starting from that copy avoids rewriting a structure that is easy to break.Replace the example domain
The template uses
localhostthroughout. A global replacement is enough to turn it into our domain.Terminal window sudo sed -i 's/localhost/it.fr/g' /etc/bind/db.it.frComplete the zone
Terminal window sudo nano /etc/bind/db.it.fr/etc/bind/db.it.fr ;; Forward zone for it.fr;$TTL 604800@ IN SOA dns.it.fr. root.it.fr. (2021102001 ; Serial604800 ; Refresh86400 ; Retry2419200 ; Expire604800 ) ; Negative Cache TTL;@ IN NS dns.it.fr.@ IN A 172.16.10.10dns IN A 172.16.10.10client IN A 172.16.20.20nas IN A 172.16.30.30The
Arecord namedclientis what lets you reach172.16.20.20by typingclient.it.fr.
Here are the main record types and what they associate:
| Type | What it associates | Example |
|---|---|---|
A | a name to an IPv4 address | nas IN A 172.16.30.30 |
AAAA | a name to an IPv6 address | nas IN AAAA 2001:db8::30 |
CNAME | a name to another name, never to an address | www IN CNAME dns.it.fr. |
MX | a domain to its mail server, with a priority | @ IN MX 10 mail.it.fr. |
TXT | free text, used by SPF, DKIM and domain validations | @ IN TXT "v=spf1 -all" |
NS | a zone to the server authoritative for it | @ IN NS dns.it.fr. |
SOA | the administrative parameters of the zone, one per zone | see above |
SRV | a service to a host and a port | _ldap._tcp IN SRV 0 5 389 dns.it.fr. |
PTR | an address to a name, the reverse of the A type | 10.10 IN PTR dns.it.fr. |
The reverse zone
The forward zone answers “what is the address of nas.it.fr”. The symmetric question, “which name matches 172.16.30.30”, belongs to a separate zone. Mail servers, system logs and many administration tools rely on it to display names rather than addresses.
Copy the reverse template
Terminal window sudo cp /etc/bind/db.127 /etc/bind/db.it.fr.invWrite the PTR records
Terminal window sudo nano /etc/bind/db.it.fr.inv/etc/bind/db.it.fr.inv ;; Reverse zone for 172.16.0.0/16;$TTL 604800@ IN SOA dns.it.fr. root.it.fr. (2021102001 ; Serial604800 ; Refresh86400 ; Retry2419200 ; Expire604800 ) ; Negative Cache TTL;@ IN NS dns.it.fr.10.10 IN PTR dns.it.fr.20.20 IN PTR client.it.fr.30.30 IN PTR nas.it.fr.The left-hand part is what remains of the address once the zone name is removed, itself reversed. For
172.16.20.20in the16.172.in-addr.arpazone,20.20is left. The value on the right is a full name: the trailing dot is mandatory.
Both zones exist on disk, but Bind9 does not know yet that they concern it.
Step 4: declare the zones and the forwarders
Declare the zones
Terminal window sudo nano /etc/bind/named.conf.local/etc/bind/named.conf.local //// Do any local configuration here//// Consider adding the 1918 zones here, if they are not used in your// organization//include "/etc/bind/zones.rfc1918";zone "it.fr" {type master;file "/etc/bind/db.it.fr";allow-query { any; };};zone "16.172.in-addr.arpa" {type master;file "/etc/bind/db.it.fr.inv";allow-query { any; };};Each block ties a zone name to its file.
type mastermeans this server holds the reference copy and has nobody to query for that zone: that is what makes it authoritative.allow-query { any; }lets any machine ask the question.Configure the forwarders
Terminal window sudo nano /etc/bind/named.conf.options/etc/bind/named.conf.options 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/800113forwarders {1.1.1.1;8.8.8.8;};//========================================================================// If BIND logs error messages about the root key being expired,// you will need to update your keys. See https://www.isc.org/bind-keys//========================================================================dnssec-validation auto;auth-nxdomain no; # conform to RFC1035version none;listen-on-v6 { any; };};forwarderslists the servers to hand over questions that do not concernit.fr. That is what lets machines on the network reach the Internet while having a single DNS server configured.
Everything is written. What remains is checking before going live, which is far quicker than diagnosing a service that refuses to start.
Verify and troubleshoot
Bind9 ships two check commands that parse the files without touching the service. Use them systematically before any restart.
Check the syntax
Terminal window sudo named-checkconfsudo named-checkzone it.fr /etc/bind/db.it.frsudo named-checkzone 16.172.in-addr.arpa /etc/bind/db.it.fr.invExpected output zone it.fr/IN: loaded serial 2021102001OKzone 16.172.in-addr.arpa/IN: loaded serial 2021102001OKnamed-checkconfprints nothing at all when the configuration is correct. Eachnamed-checkzonemust end withOKand show the loaded serial number.Restart the service
Terminal window sudo systemctl restart bind9sudo systemctl status bind9Query the server
Terminal window dig @127.0.0.1 client.it.frExcerpt of the answer ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1;; ANSWER SECTION:client.it.fr. 604800 IN A 172.16.20.20The
aaflag (authoritative answer) confirms the answer really comes from our zone and not from a cache.Terminal window dig @127.0.0.1 -x 172.16.20.20Excerpt of the answer ;; ANSWER SECTION:20.20.16.172.in-addr.arpa. 604800 IN PTR client.it.fr.Terminal window dig @127.0.0.1 example.comAn answer here, with no
aaflag, proves the forwarders work.
If one of these tests fails, the table below covers the most frequent causes.
| Symptom | Likely cause | Check |
|---|---|---|
named-checkzone reports not at top of zone | a trailing dot missing or one too many | reread every full name in the zone |
| The service refuses to start | a declared zone whose file is missing or invalid | sudo journalctl -u bind9 -n 50 |
SERVFAIL on a domain name | zone not loaded, or file unreadable by the bind user | named-checkconf, then the permissions on /etc/bind |
NXDOMAIN on a name that is present | Serial not incremented, old version still cached | increment the Serial then sudo rndc reload |
| Internal names answer, the outside does not | forwarders missing, or looping on the server’s address | the forwarders block in named.conf.options |
The client resolves nothing while dig @127.0.0.1 works | the client is not querying this server | nameserver in its /etc/resolv.conf |
The service answers in both directions for it.fr and relays the rest outside. It can now act as a resolver for the other machines on the network, starting with those in the following articles of the series.