The Domain Name System (DNS) plays a fundamental, yet often underappreciated, role in the internet’s infrastructure. It acts as a hierarchical and distributed translation service, converting human-memorable domain names (e.g., example.com) into numerical IP addresses (e.g., 192.0.2.1) that computers utilize for network communication. This critical function enables seamless user interaction with online resources.
Beyond core address translation, DNS offers additional functionalities. It directs email servers where to deliver email and facilitates secure communication channels. Furthermore, DNS can perform “reverse lookups,” identifying the domain name associated with a specific IP address. In essence, DNS serves as a versatile information directory for the internet, capable of managing diverse data types.
How does it work?
Instead of a single centralized repository, the Domain Name System (DNS) employs a distributed database architecture. This geographically dispersed structure utilizes a hierarchy of name servers, starting with the root servers overseen by the Internet Corporation for Assigned Names and Numbers (ICANN). These root servers point to authoritative name servers for top-level domains (TLDs) like “.com,” which in turn delegate responsibility to name servers for subdomains like “google.com.” This hierarchical delegation ensures scalability and fault tolerance within the DNS infrastructure.
Types of DNS servers
The Domain Name System (DNS) relies on two distinct server types.
- Authoritative DNS Servers: These servers act as the source of truth for specific domains. They hold and respond with official information for a particular domain name, configured by the domain’s owner.
- Recursive DNS Servers (also referred to as “resolvers”): These servers act as intermediaries, traversing the DNS hierarchy and querying authoritative servers to locate the records for a requested domain name. To optimize performance, recursive servers often cache retrieved information, hence the term “caching recursive DNS servers.”
Why run your own authoritative DNS server?
There are several reasons to run your own authoritative nameserver, such as increased security, control over record types, and avoiding reliance on a third-party service. However, it’s important to consider the downsides like maintenance burden and potential for outages before deciding to self-host. Julia Evans put together an excellent blog post on this topic, which I highly recommend you to read before proceeding.
Objective
Our goal is to establish a robust and secure DNS infrastructure capable of hosting multiple domain names. This infrastructure should prioritize the following key characteristics:
- Performance: We aim for fast response times to ensure efficient user experience.
- Security: The infrastructure should be secure and incorporate DNSSEC for cryptographic domain signing.
- Scalability: The system should be adaptable to accommodate future growth in the number of hosted domains.
- Maintainability: Straightforward management practices are essential for ongoing system upkeep.
- Availability: We require the DNS servers to be accessible over both IPv4 and IPv6 protocols.
Prerequisites
Assuming that you have already registered a domain name, deploying a robust authoritative nameserver setup requires a minimum of two dedicated servers. These machines should adhere to the following criteria:
- Static IP Addresses: Assigning static IP addresses ensures consistent network identification for your servers.
- Reliable and Permanent Internet Connectivity: Uninterrupted and dependable internet access is crucial for server uptime.
- Geographically Diverse Networks: Ideally, the servers should reside on distinct networks, geographically separated. Avoid co-locating them with the same internet service provider (ISP) or within the same data center. This redundancy minimizes the impact of localized network outages or infrastructure disruptions.
Fortunately, achieving this configuration is relatively straightforward. Utilizing two virtual private servers (VPS) from geographically dispersed locations and different providers achieves the recommended network separation. Furthermore, the resource demands for a DNS server are minimal. A VPS equipped with 512 MB of RAM, a contemporary CPU, and 10 GB of disk space (primarily consumed by the operating system) is sufficient for most deployments.
While this tutorial focuses on Debian, the concepts are generally applicable to most Linux distributions. The main difference you’ll encounter is the specific command for installing the DNS server and the location of its configuration files.
Installation
While numerous DNS server options exist, BIND stands out for its robustness and extensive feature set. Pioneering the field since the early 1980s, it remains a cornerstone of DNS infrastructure. A significant rewrite in 2000 with version 9 solidified its position. BIND boasts exceptional stability, versatility, and robust security configurations. Don’t be intimidated by its extensive options; achieving the configuration outlined in our objectives is still quite manageable.
On Debian, BIND can be installed as follows:
$ sudo apt update && sudo apt install bind9
All BIND configuration files reside in the /etc/bind
directory. However, the Debian package includes numerous pre-configured files, most of which are unnecessary for our purposes. To ensure a clean setup, we’ll begin with a fresh slate. We’ll only retain two essential files: /etc/bind/rndc.key
and /etc/bind/bind.keys
. The rest can be safely removed.
$ sudo rm /etc/bind/db.* /etc/bind/named.conf* /etc/bind/zones.rfc1918
Configuration
BIND can be configured through a single configuration file, /etc/bind/named.conf
. Let’s create it:
options {
directory "/var/cache/bind";
listen-on { 127.0.0.1; 192.0.2.1; };
listen-on-v6 { ::1; 2001:db8::1; };
recursion no;
allow-transfer { none; };
rate-limit {
responses-per-second 10;
};
hostname none;
version none;
server-id none;
};
zone "example.com" {
type master;
file "/var/lib/bind/example.com.zone";
allow-transfer { 192.0.2.2; 2001:db8::2; };
dnssec-policy "default";
inline-signing yes;
};
zone "example.net" {
type secondary;
file "example.net.zone";
masters { 192.0.2.3; 2001:db8::3; };
};
Options Configuration
Let’s look at the options
configuration first. The following specifies the working directory of the server. Any non-absolute pathnames in the configuration file are taken as relative to this directory. This is also the default location for most server output files:
directory "/var/cache/bind";
Next we specify the IP addresses that the server will be listening on. We specify addresses for both IPv4 and IPv6.
listen-on { 127.0.0.1; 192.0.2.1; };
listen-on-v6 { ::1; 2001:db8::1; };
This setting is an important security measure:
recursion no;
A recursive DNS server exposed to the internet can be abused in a DNS amplification attack. In this type of attack the attacker sends a large volume of DNS requests to the open resolver, spoofing the IP address of the target server. The open resolver then sends a much larger response to the spoofed IP address, overwhelming the target server with traffic. Disabling this functionality, which we don’t need on an authoritative DNS server, mitigates the risk.
Next, we specify who can perform zone transfers:
allow-transfer { none; };
Zone transfers keep your domain’s records in sync across multiple servers. Normally, you only configure records on the primary server. Secondary servers then automatically copy this data using zone transfers. This setting restricts zone transfers by default, for extra security. We’ll later specify who can transfer for each domain.
Further restricting potential attack surfaces, we rate-limit responses per network block. This throttles amplification attacks even though recursion is already disabled:
rate-limit {
responses-per-second 10;
};
For your security, we’ve tightened BIND’s default settings to prevent revealing our software version and hostname. This reduces the risk of potential vulnerabilities being exploited:
hostname none;
version none;
server-id none;
Zone Configuration
We define the configuration for each domain using the zone
sections of the configuration. First we specify that we will be the primary domain server for the example.com domain:
type primary;
For example.net we are the secondary name server:
type secondary;
Next we specify the file that will contain our zone (domain) data. Our domain will be DNSSEC signed, and for that BIND needs to be able to dynamically update it. BIND keeps all dynamic update data in /var/lib/bind
, so we put our zone data there, too:
file "/var/lib/bind/example.com.zone";
For a secondary zone, we don’t specify the directory, so the file is written into the /var/cache/bind directory:
file "example.net.zone";
In the options configuration, zone transfers were disabled. This configuration snippet specifies what IP addresses zone transfer for the example.com domain can come from. This should be the IP addresses of all our secondary name servers.
allow-transfer { 192.0.2.2; 2001:db8::2; };
For a secondary zone, we instead have to tell our name server where to pull the data from. These should be the IP addresses of our primary name server for the example.net zone:
masters { 192.0.2.3; 2001:db8::3; };
Last, we want to enable DNSSEC signing of our primary zone data. This is not needed on the secondary zone, because the zone will be signed by the primary name server.
The dnssec-policy
option defines how to sign the zone data, for simplicity we just use the default values for now. The "inline-signing"
option allows BIND to sign zones completely transparently. It will load unsigned zone data, and create a signed version of it which answers all queries and transfer requests, without altering the original unsigned version.
dnssec-policy "default";
inline-signing yes;
The zone file
Now that we have our configuration in place, the last missing piece is the actual data we want to server for the domain. This is written into the “zone file”, which our configuration places in /var/lib/bind/example.com.zone
.
Here is a very simple example of a zone file:
$TTL 3600 ; 1 hour
example.com. IN SOA ns1.example.com. dns.example.com. (
2024062510 ; serial
43200 ; refresh (12 hours)
7200 ; retry (2 hours)
604800 ; expire (1 week)
3600 ; minimum (1 hour)
)
IN NS ns1.example.com.
IN NS ns2.example.com.
IN MX 10 mail.example.com.
IN A 192.0.2.1
IN AAAA 2001:db8::1
ns1 IN A 192.0.2.53
ns2 IN A 192.0.2.54
www IN CNAME example.com.
There are many different types of DNS records with distinct purposes. The most common ones are:
- SOA (Start of Authority) Record: Provides essential domain information, including the primary domain server’s name, the administrator’s email address, the serial number, and various timers.
- NS Records: Indicate the host names of the authoritative name servers for the domain.
- A Records: Define the mapping between a domain name and an IPv4 address.
- AAAA Records: Define the mapping between a domain name and an IPv6 address.
- MX Records: Specify the mail servers responsible for receiving email for the domain.
- TXT Records: Store arbitrary text information for various purposes.
- CNAME Records: Create aliases by mapping one domain name to another.
- There are additional, less commonly used records that we will not cover for simplicity.
The zone file consists of text lines, each representing a single record. Anything following a semicolon is a comment and is disregarded. Lines beginning with a dollar sign are special processing directives, which we will cover later. The columns on each line are arranged in the following order:
- The first column indicates the record’s name, which specifies the domain it pertains to. If the name doesn’t end with a dot, it is considered relative to the domain name from the previous line.
For instance, if the first line reads “example.com.” and the second line shows “test”, then the actual domain for the second line’s record is “test.example.com.”. If the name is left empty, it defaults to the domain from the last non-empty line.
In this example, the SOA record is named “example.com.”, so all subsequent records with empty names also apply to “example.com.”. The final line, however, specifies a record for “www.example.com.”. - The second column represents the record class. Historically, this was more relevant, but nowadays, we primarily use the IN (short for “Internet”) class. BIND also includes some built-in records under the CH (“Chaos”) class to differentiate them from standard records.
- The third column is the type of the record, which we previously described.
- Finally, we have the record’s contents, which can be an IP address, another domain name, or text, depending on the record type. If the data is lengthy and spans multiple words, it can be split across several lines by starting with an open bracket, listing each word individually on separate lines, and ending with a closing bracket. The SOA record shown earlier follows this format.
Now that we better understand the file’s structure, let’s revisit our example.
It begins with an SOA record for the domain example.com, where the primary domain server is identified as ns1.example.com. The serial number is crucial because it helps synchronize the primary and secondary servers. Each time the zone content is updated, this number should be incremented. This increment alerts the secondary server that the zone data has been modified and needs updating. The specific number is not important, as long as it continues to increase with each update. Note that the email address in the record is specified using dots. For example, dns@example.com would be written as dns.example.com.
The two lines of NS records list the names of the domain name servers, which are authoritative for our domain. Note that the two records specify the names (ns1 and ns2), which are later mapped to IP addresses using A records.
The MX record designates the server responsible for receiving email for the domain. The first column indicates the priority of the server. Multiple MX records can exist, and the priority determines the order in which mail delivery attempts will be made.
The A and AAAA records will be used for our web server when a visitor wants to access our website using the naked domain name (example.com).
The CNAME line maps the www name to the IP addresses of our web server. A lookup for www.example.com will return 192.0.2.1 and 2001:db8::1 CNAME records are unique in that they cannot share a name with other DNS records. Unlike a nickname, which can be used for multiple people, a CNAME can only belong to one name. This means you can’t set a direct IP address for www.example.com and then create a CNAME from example.com to point to it. Doing so will disrupt your domain, so it’s important to understand this limitation when configuring your DNS settings.
Putting it all together
We have created a configuration file and populated the zone data. Now we need to start the BIND daemon. Note that the following command might be different if you don’t use Debian or another Linux distribution that used systemd.
$ sudo systemctl enable --now named
BIND should now be running and serving our domain data locally. We can test using the “dig” command:
$ dig example.com. a @127.0.0.1 +short
192.0.2.1
Let’s also check that the answer is coming from our name server:
$ dig example.com. soa @127.0.0.1 +short
ns1.example.com. dns.example.com. 2024062259 43200 7200 604800 3600
Make sure you open up any firewall for UDP and TCP port 53. You should now be able to query your shiny new authoritative name server remotely using the same dig command, but specifying its public IP address instead of 127.0.0.1.
Delegating authority
While your name servers are now up and running, delegating authority requires a couple of extra steps.
Typically, you’d update your domain registrar’s name server settings to ns1.example.com and ns2.example.com. Here’s the catch: since these name servers are part of your domain, we have created a chicken-and-egg situation. To resolve this, add “glue records” at your registrar. These records, pointing to your name servers’ IP addresses, help the internet find them despite being under your domain.
Once the records are updated, it can take anywhere from 2 to 48 hours for changes to be visible. How long it takes depends on the TTL of your DNS records and the TTL configuration of the top level domain. For example, .com has a TTL of 86400 seconds, so typically updates will take a day. Other domains have a much shorter TTL, for example .org uses 3600 seconds (1 hour).
You can use a DNS propagation checker to see if the update has gone through. Keep in mind though, each recursive DNS server caches its own records. Just because the DNS propagation checker says that the records have updated, it doesn’t mean that every single cache in on every single DNS server on the Internet has expired your DNS entries.
There’s one last missing piece of the puzzle. While we have DNSSEC-signed our zone, there is no way for anyone else to verify our signature. Effectively we are currently self-signing our zone, which is not worth much.
Enabling DNSSEC
To get DNSSEC working, we need to publish a DS record in the top level domain. This record contains the cryptographic public key that can be used to verify that your name server is indeed serving the correct data. The DS record has to be generated from the private key that BIND has generated for us when signing our zone.
Log back into the primary name server for your domain and check the /var/cache/bind directory. There should be a number of files starting with Kexample.com in this directory. We need to find the file that contains the key-signing key:
$ head -1 *.key
==> Kexample.com.+013+04234.key <==
; This is a zone-signing key, keyid 4234, for example.com.
==> Kexample.com.+013+22674.key <==
; This is a key-signing key, keyid 22674, for example.com.
We will use the dnssec-dsfromkey utility to generate our DS record from that:
$ dnssec-dsfromkey Kexample.com.+013+22674.key
example.com. IN DS 22674 13 2 A5CBE9DC59593F420413AA00AA94ED5DB9B92516427886E7FD306682A82BD6FA
Now log back into your registrar and find the section for configuring DNSSEC. If there isn’t one, you might be out of luck. You will have to switch registrars or leave your zone unsigned. Contact your registrar if you are not sure.
The registrar for your domain will have a form that asks you for the following information:
- Key Tag: In our example it is 22674.
- DS Data Algorithm: In our example it is 13.
- Digest Type: In our example it is 2.
- Digest: The long string of digits and letters after the Digest Type.
IMPORTANT: Make sure your copy paste all the data exactly as displayed by dnssec-dsfromkey. Any mistake in entering this data will break your domain completely and make it unreachable on the Internet. If this happens, the only solution is to delete the DS record from your registrar and wait 2 to 48 hours for the changes to propagate.
If you did everything correctly, you need to wait for the registrar changes to propagate. This usually takes less than 15 minutes, but it might take more, depending on your registrar and the top level registry you are using. To check that your domain is now correctly DNSSEC signed, use the excellent Verisign Labs DNSSEC Debugger.
Conclusion
Configuring authoritative DNS servers can be a complex process, but understanding the fundamentals is crucial for maintaining control over your domain. By following these steps and carefully considering your domain’s specific needs, you can successfully set up and manage your authoritative DNS servers. Remember, consistency, accuracy, and regular monitoring are key to ensuring optimal performance and reliability.