Set up your own DDNS Server with bind9 and go
Tags: tutorial, linux, dns, go
Published: 2022-05-03 14:42:03

## What is DDNS used for?

Dynamic DNS (DDNS) services allow you to reach networks that aren't supplied with a static IP by their ISP.
To reliably communicate with for example your home network, the DDNS-provider usually supplies you with a (sub)domain running an ordinary DNS-server and an update-webservice. A client in the internet can then reach a home network simply by connecting to the supplied DDNS-url. To update the DNS-server the update-webservice url is placed in your router or is manually run from a device inside the home network.

See [Dynamic DNS](https://en.wikipedia.org/wiki/Dynamic_DNS) for a more in depth explanation.

## Purpose of this tutorial

There are many DDNS tutorials out there. But I found it really difficult to find one thats simple, coherent and up-to-date. I would like to summarize my findings in this tutorial.
I will be using code snippets inspired by other tutorials but I will try to credit all the authors. If I make a mistake at giving credit feel free to contact me.

## How to implement your own DDNS configuration

**Disclaimer: I'm not an IT-security expert. If you find any vulnerabilities in this configuration please contact me!**

DDNS services often cost as much as a separate linux virtual machine server. So if you already have one why not set it up by yourself.

This tutorial is only for linux machines. In particular I did this on a *debian 11* VM.

I will be using *bind9* as it's one of the most popular DNS Servers. Install it using your favourite package manager like apt:

`# apt install bind9`

If you intend to use go for the update server install it using the [official instructions](https://go.dev/doc/install).

### DNS and firewall configuration

Go to your Domain/DNS-provider and edit the DNS-server settings as follows:

```
home.ddns.example.com 3600 IN NS ns.example.com
```

home.ddns.example.com will be the url under which you will be able to make a connection to you home network.
I'm using the subdomain `ddns` because your configuration will be more readable if intend to add multiple home networks.

```
ns.example.com 3600 IN A <Your Server IP>
```

This is the nameserver under which *your* DNS server runs. This will serve the *A*-entry (IP) for your home network.
Enter the **static** server IP of your VM.

```
update-ddns.example.com 3600 IN A <Your Server IP>
```

This entry will be the webservice that allows you to update the DNS entry for your home network. 

Make sure to allow access to port 53-udp for DNS and port 80/443-tcp (http/https) for updating the "A"-entry in your DNS server in your firewall.

### Bind9 configuration

The following is a relatively ordinary DNS configuration appart of a few exceptions.

At this point others often use the utility `dnssec-keygen` to generate a symmetric key. In my version of this tool the *hmac* algorithms weren't included. So I used the tool *tsig-keygen*.

To generate a symmetric key use:

```
# tsig-keygen ddnskey >> /secrets/ddnskey
key "ddnskey" {
	algorithm hmac-sha256;
	secret "BASE64=";
};
```

*ddnskey* is the name of the key and can be chosen as you like. This key (the value next to `secret`) is the one you pass to a DNS update-tool so it can update the DNS entries.
More about this later.
Make sure to edit the permissions to that file so that the user which later runs the webservice has *read* access to it.

Next edit `/etc/bind/named.conf` to configure bind9. This file may be at a different location if you use something else than debian.

Add the following contents to the file:
```
key "ddnskey" {
        algorithm hmac-sha256;
        secret "BASE64=";
};

zone home.ddns.example.com. {
    type master;
    file "/var/lib/bind/db.home.example.com";
    allow-update { key ddnskey; };
};
```

The first entry is the key output we just generated. The second entry is the definition of a new DNS zone. The zone still has to be defined in the file specified next to `file`. I had permission issues when creating the file in the `/etc/bind` folder as bind is executed with it's own user. zone-files usually have to go into the folder specified above.

Let's create that file:
```
$ORIGIN .
$TTL 200        ; 3 minutes 20 seconds
home.ddns.example.com IN SOA ns.example.com. alex.example.com. (
                                6          ; serial
                                500        ; refresh (8 minutes 20 seconds)
                                500        ; retry (8 minutes 20 seconds)
                                86400      ; expire (1 day)
                                500        ; minimum (8 minutes 20 seconds)
                                )
                        NS      ns.example.com.
                        A       1.2.3.4
```

I used the zone file [here](https://linux.robert-scheck.de/netzwerk/eigener-dyndns-dienst/) as a base template. Zone files aren't the main subject of this tutorial but they basically represent the actual DNS-Server configuration for that specific zone. This information can be accessed by querying the server. They have strict formatting rules. I found a [web tool](http://zonefile.org/) that makes it easier to work with them.

The important parts are that the TTL should be relatively short because the IP might be very short lived. In the A entry just enter anything as this will be overwritten by our update-script and will hopefully always represent the IP of the home network.

Replace `alex.example.com.` with your email.

Reload the bind9 service with:

`# systemctl restart bind9`

and test with

```
nsupdate -k /secrets/ddnskey 
> server ns.example.com
> update delete home.ddns.example.com A
> update add home.ddns.example.com 200 A 1.2.3.4
> send
```

This does the following:

1. Connect to specified nameserver
2. Delete the previous A-entry for your home network Ip
3. Set a new A-entry
4. Submit

If you have the `dnsutils` package installed you can check if the A entry was updated with

```
dig +trace home.ddns.example.com @8.8.8.8
```

I found a script in another tutorial that I slightly modified but I unfortunately didn't find the authors again. Please let me know if you know the author and I will credit them.

The following is the aforementioned script:

```
KEYFILE=/secrets/ddnskey
NAMESERVER="ns.example.com"
ZONE="home.ddns.example.com"
TTL="200"

if [ -z "$1" ]
then
  IPADDR="`curl -s ifconfig.me`"
else
  IPADDR="$1"
fi

(
  echo "server $NAMESERVER"
  echo "update delete $ZONE A"
  echo "update add $ZONE $TTL A $IPADDR"
  echo "send"
) | nsupdate -k /secrets/ddnskey

# EOF
```

If you want to update your server per http(s): I've written a [go server](https://github.com/haasal/ddns-update-server) for this purpose.