Based on Debian 11 "Bullseye" environment.

DNS

Managing DNS servers

In short, I don't recommend managing the servers for the production environment.

DNS servers were expected to return IP addresses for the domain names when the internet life was simple. Now it should provide much more complicated information using TXT records. Furthermore, Let's Encrypt with DNS challenge automatically updates records (to be machine-readable), and DNSSEC key rotation can be (should be) automated, too. (See the automatically updated zone file example at the bottom of this article.)
The simple DNS system used to require only one or two servers, but the current system requires at least four servers.
This requirement is too much, and DNSSEC involves other servers that are working in production mode.

Now we have many simple, reasonable, and stable services for DNS with full functionality. For the production environment, I recommend using those kinds of reliable services for safety and security.

Still, learning the basics of DNS should help you understand how the internet works. So I preserve this article to explain how I set up the simple DNS servers.
I hope this will help somebody in the future.

There are some options for DNS servers. BIND is one of the most popular with the full of functionalities, which is too much for me.
NSD and Knot DNS are simple, lightweight, and reliable alternatives for BIND. Since Knot DNS can automatically take care of DNSSEC keys and signings, I chose Knot DNS this time.
(I used NSD for several years without any problem. If you want, NSD can be a good option, too.)


Install

Here I used the official Debian package, version 3.0.5. The official documents are available here.
(Official stable releases are available from Knot official page.)

# apt install knot

Open ports for DNS service.

# firewall-cmd --add-service=dns --permanent
# firewall-cmd --reload

Configuration

knot.conf

Update the config file: /etc/knot/knot.conf

server:
    rundir: "/run/knot"
    user: knot:knot
    listen: [ 0.0.0.0@53, ::@53 ] # Change IP address to accept the query from any

* snip *

template:
  - id: default
    storage: "/var/lib/knot"
    semantic-checks: on # Add this line for extra checks
    file: "%s.zone"
  • The configuration for each domain should be stored in /var/lib/knot with the name "example.jp.zone"
  • The line "semantic-checks: on" is added to enable the extra checks for zone files

Add domain names to the "zone:" area. The default settings are already set in the "template:" section, only the domain line is required.

zone:
  - domain: example.jp

Zone file

The zone file example is on the Knot GitLab. Set up your domain zone files according to this example.
(Please refer to the other websites for the explanation of each line.)

$ORIGIN example.com.
$TTL 3600

@	SOA	dns1.example.com. hostmaster.example.com. (
		2010111213	; serial
		6h		; refresh
		1h		; retry
		1w		; expire
		1d )		; minimum

	NS	dns1
	NS	dns2
	MX	10 mail

dns1	A	192.0.2.1
	AAAA	2001:DB8::1

dns2	A	192.0.2.2
	AAAA	2001:DB8::2

mail	A	192.0.2.3
	AAAA	2001:DB8::3

After knot.conf and zone files are ready, restart Knot.

# systemctl restart knot

When bind-address (listen address) is changed, Knot has to be restarted.
In other cases, knotc command will work.

For the configuration (knot.conf) and zone files reload,

# knotc reload

For the zone files reload only,

# knotc zone-reload

For more information, please refer to the Knotc command help.

Check

Check if Knot will answer the query as expected.

$ dig example.jp @localhost

; <<>> DiG 9.16.15-Debian <<>> example.jp @localhost
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27190
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.jp.                    IN      A

;; ANSWER SECTION:
example.jp.             86400   IN      A       192.0.2.3

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Sat Aug 28 23:57:51 JST 2021
;; MSG SIZE  rcvd: 60

Primary and Secondary servers

They used to be called "Master and Slave" before. Whatever the names are, it's easy to set one as the primary server that you update the zone files, the other one as the secondary server that automatically receives the updates from the primary server.
If you want more robust DNS servers, multiple secondary servers can be set up. If all of them automatically receive the latest data from the primary, you don't have to update zone files here and there.

It is possible to configure "Server A to be primary for example.com, Server B to be primary for example.net, and they send the latest zone information to each other" but you will have to struggle with automatically updated zone files in a machine-readable format.

TSIG key

TSIG key can be generated by the keymgr command.

# keymgr -t key01

Then you will get the generated key and configuration on the screen.

# hmac-sha256:key01:799BI/gZTo1wvoX1D8PRGcKbfQ22N4HvQeq5rpmmZV8=
key:
  - id: key01
    algorithm: hmac-sha256
    secret: 799BI/gZTo1wvoX1D8PRGcKbfQ22N4HvQeq5rpmmZV8=

Copy and paste the configuration to the knot.conf.
Note: Never use the secret displayed here. Please generate your own key.

Primary configuration

Knot will run as a primary server unless explicitly configured to be a secondary server. Add remote (secondary) server address, key, acl sections and modify template section to use them.

/etc/knot/knot.conf

key:
  - id: key01
    algorithm: hmac-sha256
    secret: 799BI/gZTo1wvoX1D8PRGcKbfQ22N4HvQeq5rpmmZV8=

remote:
  - id: secondary
    address: [ 2001:db8::1, 192.168.2.0 ]
    key: key01

acl:
  - id: allow_transfer
    address: [ 2001:db8::1, 192.168.2.0 ]
    key: key01
    action: transfer

template:
  - id: default
    storage: "/var/lib/knot"
    semantic-checks: on
    file: "%s.zone"
    notify: secondary   # Specify the server to send out notifications
    acl: allow_transfer # Apply ACL

Reload knot to apply the new configuration.

# knotc reload

Secondary configuration

After installation, zone files are not required for the secondary servers because they can get information from the primary server.
The TSIG key secret must be exactly the same as the primary.

key:
  - id: key01
    algorithm: hmac-sha256
    secret: 799BI/gZTo1wvoX1D8PRGcKbfQ22N4HvQeq5rpmmZV8=

remote:
  - id: primary
    address: [ 2001:db8::1, 192.168.2.0 ]
    key: key01

acl:
  - id: allow_notify
    address: [ 2001:db8::1, 192.168.2.0 ]
    key: key01
    action: notify

template:
  - id: default
    storage: "/var/lib/knot"
    file: "%s.zone"
    master: primary   # Specify the master server
    acl: allow_notify # Apply ACL

If secondary servers don't get the latest information, force retransfer.

# knotc zone-retransfer

Automatically updated zone file

Here are how zone files look different on the primary server and the secondary server.

On the primary server, it looks as the example introduced above.

$ORIGIN example.com.
$TTL 3600

@       SOA     dns1.example.com. hostmaster.example.com. (
                2010111213      ; serial
                6h              ; refresh
                1h              ; retry
                1w              ; expire
                1d )            ; minimum

        NS      dns1
        NS      dns2
        MX      10 mail

dns1    A       192.0.2.1
        AAAA    2001:DB8::1

dns2    A       192.0.2.2
        AAAA    2001:DB8::2

mail    A       192.0.2.3
        AAAA    2001:DB8::3

On the secondary server, each record will have complete information.

example.com.      3600    SOA     dns1.example.com. hostmaster.example.com. 2010111213 21600 3600 604800 86400
example.com.      3600    NS      dns1.example.com.
example.com.      3600    NS      dns2.example.com.
example.com.      3600    MX      10 mail.example.com.
dns1.example.com. 3600    A       192.0.2.1
dns1.example.com. 3600    AAAA    2001:DB8::1
dns2.example.com. 3600    A       192.0.2.2
dns2.example.com. 3600    AAAA    2001:DB8::2
mail.example.com. 3600    A       192.0.2.3
mail.example.com. 3600    AAAA    2001:DB8::3

It's still human-readable, but updating with this style requires a bit too much work. If you have long TXT records such as DomainKeys, it will be more complicated.
This is one of the reasons to determine one server as the primary to be updated manually.

BUT, the records above will help you when you use DNS services such as Amazon Route53 or Cloudflare.


Update History

2021-08-27

  • Add some explanation why I don't recommend managing the DNS servers for the production environment.
  • Change firewall configuration from UFW to firewalld
  • Change "Master and Slave" to "Primary and Secondary"
  • Add "Automatically updated zone file" section to show the example