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.
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:
-
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.infoHow 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.
-
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 subdomainexample.org
. -
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 -
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.
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.
-
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 useddig
without any options, since these are the default ones. -
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. +shortThe 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
.noteThe option
+short
returns only the ANSWER section. -
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 subdomainfs.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 +shortnoteIt 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. -
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 ofNS
), 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.
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:
- https://www.buddyns.com/activation/
- https://1984hosting.com/product/freedns/
- https://puck.nether.net/dns/
- https://freedns.afraid.org/
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.
-
Get the scripts and initialize a directory for the container:
ds pull nsd
ds init nsd @nsd -
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
.
-
Edit
/etc/systemd/resolved.conf
, uncommentDNSStubListener
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. -
On
/etc/systemd/resolved.conf
, uncomment also the line that starts withDNS=
and set a suitable value to it, for exampleDNS=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 -
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
-
Reboot the system:
reboot
-
Check that port
53
is now free, then remake the NSD container:lsof -i :53
cd /var/ds/nsd/
ds make
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.
-
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
"noteWe can find the third option of
SECONDARY_NS
at: https://www.buddyns.com/support/setup/zone-delegation/free/ -
Rebuild the container:
ds make
3. Manage domains
3.1 Add a domain
Let's manage the domain user1.fs.al
with this nameserver.
-
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.comHow to set these nameservers depends on the DNS provider and its management interface.
infoFor the domain
user1.fs.al
, the configuration is done on the primary NS of the domainfs.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 domainuser1.fs.al
. -
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 -
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. -
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 +shortnoteIt 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
).
-
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 -
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 -
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
- Domain:
3.4 Remove a domain
-
Remove it from each secondary nameserver service.
-
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
tozones/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
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.
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:
-
For each domain that you manage, go to the website of the provider of the domain and update the list of the nameservers.
-
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.
-
On the primary nameserver, update
settings.sh
accordingly and then runds make
to update the configuration files.