Navigation: ${HOME} /rss.xml /github /Gab

OpenBSD as an authoritative DNS nameserver

Configuring NSD on OpenBSD as an authoritative DNS nameserver

OpenBSD ships with the NLnet Labs Name Server Daemon - nsd(8), a fast and secure (DNSSEC-enabled) implementation of an authoritative DNS nameserver.

First we should generate a TSIG (Transaction SIGnature) key. If using hmac-md5:

dd if=/dev/urandom of=/dev/stdout count=1 bs=32 | openssl base64

Or for sha256 (preferred):

dd if=/dev/urandom of=/dev/stdout count=1 bs=64 | openssl base64

Keep the resulting base64-encoded key for later. For demonstration purposes I will be using the following sha256 key:


Open /var/nsd/etc/nsd.conf and create a simple configuration for our example domain:

        hide-version: yes
        verbosity: 1
        database: "" # disable database

        control-enable: yes
        control-interface: /var/run/nsd.sock
        server-key-file: "/var/nsd/etc/nsd_server.key"
        server-cert-file: "/var/nsd/etc/nsd_server.pem"
        control-key-file: /var/nsd/etc/nsd_control.key"
        control-cert-file: "/var/nsd/etc/nsd_control.pem"

   name: "sec_key"
   algorithm: hmac-sha256 # or hmac-md5
   secret: "0i96GKeAPxwGZ2ALxrvM882oL107NuCnXLjv4PRpzCS31oySYILYzbs02Aes0OqCgy5+rA96YGep2xFWmzsKHg=="

        name: ""
        zonefile: "master/"
        notify: sec_key
        provide-xfr: sec_key

The IP in the last two lines should be that of your slave. If you are configuring the slave, this IP should be that of the master.

The default base location (OpenBSD users rarely deviate from good defaults!) for zonefiles is /var/nsd/zones so we create the file /var/nsd/zones/master/

$ORIGIN    ; default zone domain
$TTL 86400                    ; default time to live

@ IN SOA (
           2018010203  ; serial number
           28800       ; Refresh
           7200        ; Retry
           864000      ; Expire
           86400       ; Min TTL

@        MX    10
www     IN      A
mail    IN      A
@       IN      A

See RFC 1034 and RFC 1035 if you are unfamiliar with the zone file format.

Next generate the SSL keys for nsd(8):

$ doas nsd-control-setup
setup in directory /var/nsd/etc
generating nsd_server.key
Generating RSA private key, 3072 bit long modulus
e is 65537 (0x10001)
generating nsd_control.key
Generating RSA private key, 3072 bit long modulus
e is 65537 (0x10001)
create nsd_server.pem (self signed certificate)
create nsd_control.pem (signed client certificate)
Signature ok
Getting CA Private Key
Setup success. Certificates created. Enable in nsd.conf file to use

Check your configuration file contains no errors - this is good practice on a live production server before reloading the config:

$ doas nsd-checkconf /var/nsd/etc/nsd.conf

Run nsd(8) in the foreground to check everything is working:

    $ doas nsd -d -V 5
    [2018-10-31 15:51:02.541] nsd[12021]: notice: nsd starting (NSD 4.1.25)
    [2018-10-31 15:51:02.542] nsd[12021]: info: creating unix socket /var/run/nsd.sock
    [2018-10-31 15:51:02.633] nsd[76579]: info: zone read with success
    [2018-10-31 15:51:02.711] nsd[76579]: notice: nsd started (NSD 4.1.25), pid 12021

Now use dig(1) to check that it is serving lookup requests for our new domain:

voyager$ dig ANY
;; Truncated, retrying in TCP mode.

; <<>> DiG 9.4.2-P2 <<>> ANY
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48761
;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;             IN      ANY

;; ANSWER SECTION:      86400   IN      SOA 2018010203 28800 7200 864000 86400      86400   IN      NS      86400   IN      MX      10      86400   IN      A

;; ADDITIONAL SECTION: 86400   IN      A

;; Query time: 44 msec
;; WHEN: Wed Oct 31 15:52:25 2018
;; MSG SIZE  rcvd: 155

ctrl-C to kill the nsd foreground process then enable and start it as a daemon:

rcctl enable nsd
rcctl start nsd

That's it! But we haven't enabled DNSSEC for our zone...

Signing our zone with DNSSEC

For this we will need ldns-keygen from LDNS:

$ doas pkg_add ldns-utils

Now we generate keys - a zone-signing key (ZSK) and a key-signing key (KSK):

$ cd /var/nsd/zones
$ export ZSK=`/usr/local/bin/ldns-keygen -a RSASHA512 -b 2048`
$ export KSK=`/usr/local/bin/ldns-keygen -k RSASHA512 -b 4096`

DS records were automagically generated, but we will create our own later so delete them:

$ rm *.ds

Create a signed zone for - this will create master/

$ ldns-signzone -n -s $(head -n 1000 /dev/urandom | sha256 | cut -b 1-16) master/ $ZSK $KSK

Change's zonefile in /var/nsd/etc/nsd.conf to the new signed file:


Reload our nsd configuration

$ nsd-control reconfig
$ nsd-control reload

Now if we lookup our zone with dig, this time specifying DNSKEY, we should get different results that with the DNSSEC sigs:

dig DNSKEY +multiline +norec

Generate DS records for our zone and save the result to your clipboard or somewhere:

$ ldns-key2ds -n -f -2 master/      86400   IN      DS      28892 7 2 fa1b31305013e427a8dac5318fbf6ffcdbfda94309ddf12ebdca101a5e07167d      86400   IN      DS      28316 10 2 1e38d492215cd05a28b8ea64eaf42c82648064b7c563b7ea27eddd9a7e8d69d3

These records must be added at TLD level - as we're using a domain, we are covered by nominet's dns* Your domain registrar may have a form in their control panel for you to add these DS records, else you may have to contact their customer services. Once the keys have been added, you can check them using dig:

$ dig DS +trace +short | egrep '^DS'
DS 28316 10 2 1E38D492215CD05A28B8EA64EAF42C82648064B7C563B7EA27EDDD9A 7E8D69D3 from server in 29 ms.
DS 28892 7 2 FA1B31305013E427A8DAC5318FBF6FFCDBFDA94309DDF12EBDCA101A 5E07167D from server in 29 ms.

The easiest way to verify everything is working is to check the domain on

Unfortunately, this setup requires maintenance - the DNSSEC signatures will expire in four weeks (thanks @Habbie!), so some hackery with shell scripts and cron jobs is probably the best solution until something more robust is included in OpenBSD. One such example is sign-DNSSEC.

Update: Callum Smith, author of dank-selfhosted has a very clean script which can be run in a cron nightly here

To setup a slave, follow this procedure again - but replace the allow-notify and request-xfr IP with that of the master nameserver. Once both are up and running, use nsd-control(8) with the force_transfer command to test a zone transfer.

Navigation: ${HOME}

© 2003-2018 Cryogenix | Powered by OpenBSD | Built with ssg3