Skip to main content

DNS Management

1. Reset the VPS

We have done a lot of testing in the past. Before we continue with the setup of the DNS server, and the rest of the lessons, let's reset everything on the VPS, to make sure that we have a pristine environment.

If we did a snapshot of the VPS, before starting the examples, then we can rebuild the VPS from the image of the snapshot, and then install again docker and docker-scripts.

Otherwise, we can just rebuild the VPS from a Debian12 image, and setup everything from scratch, as we did before.

note

The script vps-setup.sh can help with automating most of the configuration steps, including installation of docker and docker-scripts. However it does not generate the SSH keys and does not do the SSH configuration.

wget https://linux-cli.fs.al/apps/part4/vps-setup.sh

nano vps-setup.sh
chmod +x vps-setup.sh
./vps-setup.sh
rm vps-setup.sh
vps-setup.sh
#!/bin/bash -x

main() {
upgrade
install_fail2ban
install_firewalld
customize_prompt
colorized_ls
fix_vim_background
install_utilities
install_docker
install_docker_scripts
}

upgrade() {
apt update
apt upgrade --yes
apt autoremove --yes
}

install_fail2ban() {
apt install --yes fail2ban python3-systemd

cat <<EOF > /etc/fail2ban/jail.local
[DEFAULT]
backend = systemd
EOF

systemctl restart fail2ban
}

install_firewalld() {
apt install --yes firewalld
firewall-cmd --permanent --zone=public --set-target=DROP
firewall-cmd --reload
}

customize_prompt() {
sed -i ~/.bashrc -e '/bashrc_custom/d'
echo 'source ~/.bashrc_custom' >> ~/.bashrc

cat <<'_EOF_' > ~/.bashrc_custom
# set a better prompt
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;36m\]\u\[\033[01;33m\]@\[\033[01;35m\]\h \[\033[01;33m\]\w \[\033[01;31m\]\$ \[\033[00m\]'
_EOF_
}

colorized_ls() {
sed -i ~/.bashrc \
-e 's/# export LS_OPTIONS=/export LS_OPTIONS=/' \
-e 's/# alias ls=/alias ls=/' \
-e 's/# alias ll=/alias ll=/' \
-e 's/# alias l=/alias l=/'
}

fix_vim_background() {
sed -i /etc/vim/vimrc \
-e 's/^"set background=dark/set background=dark/'
}

install_utilities() {
apt install --yes \
psmisc tmux tmate asciinema
}

install_docker() {
# get the GPG key
wget https://download.docker.com/linux/ubuntu/gpg \
-O /etc/apt/keyrings/docker.asc

# add the docker sources to the apt list
cat << _EOF_ > /etc/apt/sources.list.d/docker.sources
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: bookworm
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
_EOF_

# install docker packages
apt update
apt install --yes \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
}

install_docker_scripts() {
# install dependencies
apt install --yes git make m4 highlight tree

# get the code fro GitLab
git clone \
https://gitlab.com/docker-scripts/ds \
/opt/docker-scripts/ds

# install it
cd /opt/docker-scripts/ds/
make install
}

# call main
main "$@"

1. How DNS Works

1.1 The steps

Let's say that a client (a browser) needs to access cloud.example.org. For this, it needs to find out the address (IP) of the server. Assuming that it knows nothing about it, the client can follow these steps:

  1. Contact a root nameserver and ask it which servers are responsible for managing the top-level domain .org. There are several root nameservers, all of them are synchronized with each-other, and each of them has information about the servers responsible for each top-level domain.

    info

    How does the client know the IPs of the root nameservers? It knows because the list of these IPs is well known, and almost does not change over time.

  2. From the query on the first step the client gets a list of the servers responsible for the domain .org. Again, all of these servers are synchronized with each-other and it can ask any of them for the servers that are responsible for the subdomain example.org.

  3. From the query on the previous step it will get a list of nameservers for the domain example.org, for example:

    ns1.example.org
    ns2.example.org
    ns3.example.org
    ns4.example.org
  4. Finally, the client can ask any of these nameservers for the IP of the server cloud.example.org (and should get the same answer).

This diagram tries to show the hierarchy of the nameservers.

The hierarchy of the nameservers

1.2 An example

Let's try to follow these steps manually for a real domain, for example cloud.fs.al. We can use the command dig to make DNS queries.

  1. Get the root nameservers:

    dig NS . | less

    We get a more compact answer with the option +short:

    dig NS . +short

    The type of records we are asking for is NS (for nameserver), and we are asking them for the . (root) domain. We would get the same answer if we just used dig without any options, since these are the default ones.

  2. Get the nameservers of the top-level domain al.

    We can send a query to any of the root servers above, requesting information about the nameservers of the top-level domain al:

    dig NS al @m.root-servers.net.
    dig NS al @ns1.nic.al.
    dig NS al @ns1.nic.al. +short

    The first query return an "AUTHORITY" section, which means: go and ask any of these servers for further information. In the second query we ask one of these servers, and it returns an "ANSWER" section, which means: these are the nameservers for the TLD (Top Level Domain) al.

    note

    The option +short returns only the ANSWER section.

  3. Get the nameservers of fs.al.

    We can ask (query) any of the nameservers of the domain al to tell us what are the nameservers of the subdomain fs.al:

    dig NS fs.al @nsx.nic.al.
    dig NS fs.al @puck.nether.net.
    dig NS fs.al @puck.nether.net. +short
    dig NS fs.al +short
    note

    It we don't specify the nameserver to be asked (with the argument starting with @), a random one from the list will be asked. If it does not reply, another one will be asked, and so on.

  4. Get the address of cloud.fs.al:

    dig A cloud.fs.al @puck.nether.net. +short

    Note that the type of query now is A (instead of NS), which means that we are asking for an address (instead of a nameserver).

1.3 Synchronizing nameservers

Let's say that the nameservers of the domain example.org are:

ns1.example.org
ns2.example.org
ns3.example.org
ns4.example.org

These nameservers need to be synchronized with each-other. How do we achieve that?

One of these nameservers (for example the first one) can be primary, and the others secondary. Modification of the records is always done on the primary nameserver, and the secondary nameservers will eventually synchronize their content with the primary one.

The primary nameserver can also be another one, that is not on the list, and all the nameservers of the domain with retrieve the content from it. This is a better approach because the primary nameserver can be placed behind a firewall, forbidding access to everyone, except the secondary nameservers.

Synchronizing nameservers


When a modification on the records of the primary NS is done, it notifies the secondary NSs about it. When the secondary NSs get this notification, they send a synchronization (AXFR) request to the primary NS, in order to retrieve from it an updated list of records. Upon receiving this list, they replace the old list of records with the new one.

Notice that the clients do not send any requests to the primary NS, but only to the secondary ones, because only these are published as nameservers for the domain. They don't even know the address (IP) of the primary NS. Only the secondary nameservers know its IP. But even if a client knows the IP of the primary NS and tries to access it, it will be prevented by the firewall, because the firewall allows only the secondary nameservers to access the primary one on port 53, in order to send AXFR (synchronization) requests. Even if there was no firewall, the primary server is configured to reply only to synchronization requests from the secondary nameservers.

2. DNS setup

Now that we know how DNS works, it will be easier to setup and manage our DNS server.

2.1 Find secondary NS services

Instead of building and maintaining our own secondary nameservers, we can use services that are available either for free or for a small fee. You can do a quick search and find some that may suit your needs.

Here are some of them that offer a free service:

note

The "primary/secondary" nameservers are also called "master/slave". You may find this terminology used on these websites.

2.2 Install the primary nameserver

We will install the primary nameserver with an NSD container.

  1. Get the scripts and initialize a directory for the container:

    ds pull nsd
    ds init nsd @nsd
  2. Customize the settings and make the container:

    cd /var/ds/nsd/
    nano settings.sh
    lsof -i :53
    ds make
Make sure that port 53 is free

The NSD container that we are trying to install, needs access to the port 53 of the host. If this port is already being used by something else, there will be a conflict and the container will fail to start.

To check whether there is something listening to this port, use the command lsof -i :53. On the systems that use systemd, usually there is a service named systemd-resolved which is listening on port 53. Let’s see how to prevent systemd-resolved from using port 53.

  1. Edit /etc/systemd/resolved.conf, uncomment DNSStubListener and set it to no.

    This will free up port 53. However, we also need to enable a DNS server, otherwise we won’t be able to resolve any domain names, to download anything, to install any packages, etc. Follow the other steps to do it.

  2. On /etc/systemd/resolved.conf, uncomment also the line that starts with DNS= and set a suitable value to it, for example DNS=8.8.8.8. This file now should look like this:

    [Resolve]
    DNS=8.8.8.8
    #FallbackDNS=
    #Domains=
    #LLMNR=no
    #MulticastDNS=no
    #DNSSEC=no
    #DNSOverTLS=no
    #Cache=no-negative
    DNSStubListener=no
    #ReadEtcHosts=yes
  3. Create a symbolic link for /run/systemd/resolve/resolv.conf with /etc/resolv.conf as the destination:

    ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
  4. Reboot the system:

    reboot
  5. Check that port 53 is now free, then remake the NSD container:

    lsof -i :53
    cd /var/ds/nsd/
    ds make
note

These instructions have been tested on Ubuntu, but they should work the same on Debian or any other system having systemd version 232 or newer.

2.3 Configuration files

The variable SECONDARY_NS on settings.sh is used by the installation scripts to generate the configuration file config/secondary.ns:

ls
nano settings.sh
ls config/
nano config/secondary.ns

This file is included in a zone configuration:

nano zones/example.org.db

The variable AXFR_SERVERS is used to create the files config/notify.conf and config/provide-xfr.conf, which are used to define the pattern axfr-servers in the main config file config/nsd.conf:

nano settings.sh
nano config/notify.conf
nano config/provide-xfr.conf
nano config/nsd.conf

The IP list on AXFR_SERVERS is also used to open the port 53 on the firewall for the secondary servers:

ds exec iptables-save | grep ufw-user-input | less

2.4 Customize secondary nameservers

To customize the secondary nameservers, we should edit the constants SECONDARY_NS and AXFR_SERVERS on settings.sh. Then run again ds make, which will rebuild the container and will regenerate the configuration files mentioned above (config/secondary.ns, config/notify.conf, config/provide-xfr.conf), as well as update the firewall rules.

For the sake of demonstration, to make the setup and maintenance easier, let's use only byddyns and remove the other secondary nameserver providers.

  1. Edit settings.sh so that it looks like this:

    APP=nsd
    IMAGE=nsd
    CONTAINER=nsd

    SECONDARY_NS="
    $(: https://www.buddyns.com/support/setup/zone-delegation/free/ )
    uz5qfm8n244kn4qz8mh437w9kzvpudduwyldp5361v9n0vh8sx5ucu.free.ns.buddyns.com $(: 'USA, California' )
    uz5x36jqv06q5yulzwcblfzcrk1b479xdttdm1nrgfglzs57bmctl8.free.ns.buddyns.com $(: 'Germany, EU' )
    uz52u1wtmumlrx5fwu6nmv22ntcddxcjjw41z8sfd6ur9n7797lrv9.free.ns.buddyns.com $(: 'Japan, Asia')
    "
    AXFR_SERVERS="
    $(: https://www.buddyns.com/support/setup/zone-transfer/free/ )
    108.61.224.67
    116.203.6.3
    107.191.99.111
    185.22.172.112
    103.6.87.125
    192.184.93.99
    119.252.20.56
    31.220.30.73
    185.34.136.178
    185.136.176.247
    45.77.29.133
    116.203.0.64
    167.88.161.228
    199.195.249.208
    104.244.78.122
    "
    note

    We can find the third option of SECONDARY_NS at: https://www.buddyns.com/support/setup/zone-delegation/free/

  2. Rebuild the container:

    ds make

3. Manage domains

3.1 Add a domain

Let's manage the domain user1.fs.al with this nameserver.

  1. Set the nameservers of the domain.

    The seller of the domain usually offers the possibility to manage the records of this domain in their nameservers. However they also allow you to manage your domain with your own nameservers, which is what we want. You have to tell them what are the nameservers for the domain.

    According to settings.sh, our (secondary) nameservers are these:

    uz5qfm8n244kn4qz8mh437w9kzvpudduwyldp5361v9n0vh8sx5ucu.free.ns.buddyns.com
    uz5x36jqv06q5yulzwcblfzcrk1b479xdttdm1nrgfglzs57bmctl8.free.ns.buddyns.com
    uz52u1wtmumlrx5fwu6nmv22ntcddxcjjw41z8sfd6ur9n7797lrv9.free.ns.buddyns.com

    How to set these nameservers depends on the DNS provider and its management interface.

    info

    For the domain user1.fs.al, the configuration is done on the primary NS of the domain fs.al, and the records look like these:

    user1.fs.al.  IN  NS  uz5qfm8n244kn4qz8mh437w9kzvpudduwyldp5361v9n0vh8sx5ucu.free.ns.buddyns.com.
    user1.fs.al. IN NS uz5x36jqv06q5yulzwcblfzcrk1b479xdttdm1nrgfglzs57bmctl8.free.ns.buddyns.com.
    user1.fs.al. IN NS uz52u1wtmumlrx5fwu6nmv22ntcddxcjjw41z8sfd6ur9n7797lrv9.free.ns.buddyns.com.

    Here, records of type NS are being used, which show where are the nameservers for the domain user1.fs.al.

  2. Add a zone on the primary nameserver.

    The next step is to add a zone for this domain in the NSD container:

    cd /var/ds/nsd/

    ds zone
    ds zone add user1.fs.al

    ls zones/
    nano zones/user1.fs.al.zone
    nano zones/user1.fs.al.db
    nano config/secondary.ns

    ds zone test user1.fs.al
  3. Add the domain to each secondary NS service.

    Currently we are using only https://www.buddyns.com/ as secondary NS provider, so lets' go at https://www.buddyns.com/activation/ and add the name of the domain (user1.fs.al) and the IP of the VPS (188.245.242.143 in my case). With this we are telling BuddyNS that the primary NS for this domain is located at this server.

  4. Check that it is working:

    dig NS user1.fs.al +short
    dig user1.fs.al +short
    dig www.user1.fs.al +short
    dig xyz.user1.fs.al +short
    note

    It may take some time for the DNS modifications to propagate through the Internet.

3.2 Modify DNS records

Each time that we modify some records on the file zones/user1.fs.al.db, we should also update the serial number.

Then we notify the secondary nameservers that there are some updates with ds notify. After reloading the zones and sending notifications, it starts to trace the logs. So, it is OK to stop it with Ctrl+c after a few seconds.

Alternatively, a ds restart will also reload the zones and send notifications to the secondary nameservers.

3.3 Add another domain

Let's say that we want to manage the domain user2.fs.al as well (or example.org).

  1. Set the nameservers on the domain provider's web interface.

    Our nameservers are these:

    uz5qfm8n244kn4qz8mh437w9kzvpudduwyldp5361v9n0vh8sx5ucu.free.ns.buddyns.com
    uz5x36jqv06q5yulzwcblfzcrk1b479xdttdm1nrgfglzs57bmctl8.free.ns.buddyns.com
    uz52u1wtmumlrx5fwu6nmv22ntcddxcjjw41z8sfd6ur9n7797lrv9.free.ns.buddyns.com
  2. Add another zone at the NSD container:

    ds zone
    ds zone add user2.fs.al

    ls zones/
    nano zones/user2.fs.al.zone
    nano zones/user2.fs.al.db

    ds zone test user2.fs.al
  3. Add the domain to each secondary NS service.

    Currently we are using only BuddyNS as a secondary NS provider, so let's go at https://www.buddyns.com/buddyboard/zones/ and add another zone:

    • Domain: user2.fs.al
    • Primary Server: 188.245.242.143

3.4 Remove a domain

  1. Remove it from each secondary nameserver service.

  2. Disable or remove its configuration on the primary server:

    #ds zone rm user2.fs.al
    ds zone dis user2.fs.al
    ls zones/

    When we disable a zone, it will rename zones/example.org.zone to zones/example.org.zone.disabled, so that its configuration is not loaded by the nsd service.

4. Troubleshooting

We can make some simple checks and tests like this:

ds check
ds check --config
ds check --zones

To check the AXFR response for a domain:

ds zone
ds zone test user1.fs.al

It will actually list all the records that will be sent to a secondary nameserver.

For further troubleshooting, we can get a shell inside the container and try commands like these:

systemctl status nsd
systemctl restart nsd
tail /var/log/syslog -n 30
dig @localhost AXFR user1.fs.al
ufw status

5. Maintenance

For more details see: https://docker-scripts.gitlab.io/dns.html

5.1. Migrate the primary nameserver

To migrate the container of the primary nameserver to another host, we should transfer (with scp or rsync) the content of /var/ds/nsd/ from the old host to the new one. Then on the new host we should run:

ds pull nsd
cd /var/ds/nsd/
ds make
caution

We may also need to resolve any conflicts with the port 53 on the new host.

Now the public IP of the master nameserver has been changed (to the IP of the new host), so we should update it on the configuration of each secondary nameserver, for each domain.

note

If you are managing many domains this may be a bit tedious, because it has to be done manually, but hopefully you are not doing such a migration frequently.

5.2. Modify secondary nameservers

If you need to modify the list of secondary nameservers, for example add ns1.1984.is on the list, or remove one from the list, you should also make sure to update these things:

  1. For each domain that you manage, go to the website of the provider of the domain and update the list of the nameservers.

  2. If you are adding a new secondary nameserver, go to the website of the nameserver and make sure that you add there all the domains that you manage, along with the public IP of the primary nameserver.

  3. On the primary nameserver, update settings.sh accordingly and then run ds make to update the configuration files.