Today the 1st of November 2017, my internet domain has a new DNSSEC key (keytag 20243) and it begin to be signed with another one (keytag 45395). It is the result of my project, Ldnscript, which I am going to describe there.

stephane@blackblock:/var/ldnscripts ldnscript status
### zsk
## generated
creation:       Nov  1 05:30:01 2017
## private
creation:       Oct  1 16:05:19 2017
## retire
creation:       Oct  1 16:01:56 2017

### ksk
## generated
stat: *.generated: No such file or directory
## private
creation:       Oct  1 16:01:56 2017
## retire
creation:       Oct  1 16:01:56 2017

Last signature:         Nov  1 05:30:01 2017 /var/nsd/signed/
Zone is verified and complete

Various possibilities

There are several useful softwares available on OpenBSD to sign your DNS zone (and, sort of, run a DNSSEC zone):


… which is a clusterfuck labyrinth to me, absolutely not KISS, but does the job. I have been using it for a while and did not like it at all. It is hard to master and understand. It is in the OpenBSD port system and can be installed provided the system is clean and you are courageous enough to try it. It depends on Ldns.


… is actually another authoritative DNS server. It automatically renew keys, signs and serves the zone. It is in the ports but I never used it.


… does the same as Knot, although DNSSEC components are actually external binaries.


I have been using it for a while and I recommend it to a point. It is KISS: simple, uses filesystem hierarchy to run recursive domains. It uses dnssec-signzone and the rest of Bind tools.

The trouble with this software is that it does not evolve a lot. I don’t think we will see Edwards curves soon. Yet it would be positive. The port is not well maintained (Thuban asked for an update, which would have simply be a matter of updating to the last version of the software, no answer whatsoever).

I have been listening to other tools to run my DNSSEC zone and I have recently discovered (again) Ldns, which is moreover available in ports now.

So I started the project of designing my own tool to maintain my zone. I got help from Thuban and PengouinBSD.


This should be simple and therefor easy to understand, maintain, debug. I’d rather have a small over-consumption of resources (sign a bit too often, having a bit too many keys…) than build something overkill with a lot of bugs. DNSSEC itself is already sophisticated enough, let’s not add another layer of it.

Minimum dependencies. I am going to try to use as much as possible system tools and structures (repositories, cron, scripts monthly, weekly, daily, NSD).

This script is designed to fit simple cases, that is simple small zones with only one level. I think, maybe wrongly, that this correspond to the vast majority of domains owners, who don’t need complex softwares to run their stuff. I have therefor drawn things to fit it and hope it will do it for most users.

What this does not do

This script doesn’t know out-of-the-box how to deal with multi-layers zones. They are still possible in theory. You just have to copy the DS file’s content in the source of the parent zone.

I do not have yet a validity date for keys (signatures have though).


This will be called ldnscript. It is available in the framagit git and I hope you would contribute to it by proposing patches or reporting bugs.

It is written with OpenBSD’s Korn shell.

I place it under ISC Licence, OpenBSD’s one at this time.


You need ldns-utils package (in OpenBSD’s ports system and repositories) :

$ doas pkg_add ldns-utils

Installation can be done anywhere (/var, /usr, /etc…). It needs a repository to hold the script, the general conf’ file and the subdirectory to run the zones. My Installation runs in /var/ldnscripts, and it’s what I am going to describe here. But it should run fine everywhere. Please report the bug in case it does not.

$ cd /var
$ doas mkdir ldnscripts
$ doas chown $USER ldnscripts
$ git clone


Warning : this script is provided as-is, without any warranty. You must understand rougthly what’s happening, DNSSEC and the rest, overwise, you will break things.

The configuration is written in a conf file in its directory:


# repository where to find unsigned zone file and specific conf

# serial : unixtime
# Cannot make it other currently. I could make it YYYYMMDDnn, but how ?

# algorithm to use. They are listed : ldns-keygen -a list

# length of the zsk


# validity of signatures in days


# Verbose - set to 1 if needed

# where to search for root keys to check the zone

This file setup where to look for raw zonefiles (option NS_REP). These files should be in the classical Bind standard and the filename have to be the one of the zone.

You can change the algorithm, keys lengths, validity periods (in days only at the moment).

The SERIAL option sets the form the zone’s serial number will take. Possibilities are unixtime which will use the unix timestamp ; date which will use the date in form YYYYMMDDnn where nn is the number of changes in the day, incremental which will be a simple counter increasing by one each time.

The RUN option sets how many times the NSEC3 algorithm have to run to generate signings. I assumed you will want NSEC3 because, after all, it’s the best.


$TTL    1D
@       IN      SOA (
                        1405492580      ; Serial
                                1D     ; Refresh
                                2H      ; Retry
                                2W     ; Expire
                                2D )       ; Negative Cache TTL

@               IN      NS
@               IN      NS
@               IN      NS

@               IN      MX 10

@               IN      TXT     "v=spf1 -all"
_dmarc          IN      TXT     "v=DMARC1;p=reject;;;adkim=s;aspf=s;pct=20;rf=afrf;sp=reject"

mail            IN      A

host            IN      A
                IN      AAAA    2001:db8::45:b

router          IN      A
                IN      AAAA    2001:db8::45:dec

photos          IN      CNAME host
www             IN      CNAME host

If you use an $ORIGIN, you must set it at the top of the file, like in the example, otherwise it gets buggy. If you really want to set the $ORIGIN farther, please send me back the patch.

Each zone can have its own special configuration through a conf file of the same form and options as the generic one. The filename will be zone.conf.


# algorithm to use. They are listed : ldns-keygen -a list

# length of the zsk

Here I placed a key length of 4096 bits. This is actually somewhat rather barbarian. Please don’t do it.

You have here a rather important setting: signatures’ validity (VALIDITY option), which must be higher than your choice of signatures interval (cf the signing command later).


You need to configure NSD. All zonefiles will be written in /var/nsd/signed/$ZONE.

/var/nsd/etc/nsd.conf :

## master zones
    name: "zone.tld"
    zonefile: "signed/zone.tld"

Commands description

Ldnscript needs two arguments. First one is the action to run, the second the domain. For example:

# ldnscript ksk

If you use all as the domain name, the action will be performed for all domains managed by the script. For example, the script here will sign all domains :

# ldnscript signing all


This manage key states along time. It takes only one argument, the zone name. It is though to get run every month by the monthly script.

# ldnscript rollover

Or better (in /etc/monthly.local)…

for dir in `ls /var/ldnscripts/zones/`; do
    /var/ldnscripts/ldnscript rollover $dir

This script runs Pre-Publish philosophy: you publish keys and patiently wait they are known everywhere to activate them. And we wait for their signatures to get expired before erasing them.

The script will successively move each key on its lifeline (retired keys get erased, active keys are put to retire, keys that were generated previously are put to active state and finally a new key gets generated with the ksk|zsk command described later).

At the same time, if several keys have been generated since the last rollover, only the last generated key will get its state changed to active and all but this key will be erased (assuming that if the administrator created it, it was to use it in place of the key provided previously).

If you count correctly, a zone that you leave on its own will have three ZSK:

  • One, generated at the beginning of the current month and will be used to sign from the beginning of the next month.
  • One that is currently active.
  • One, that has been put to retirement at the beginning of this month.

This insures keys are always valid and normally propagated in validators when scripts begin to sign with it (provided the signing validity is not absurdly long). Same way, signatures will always be expired when keys get erased.

If the administrator has created a new KSK (with the following script), then the current KSK will be put to retire and the new KSK will be put to work.

Then, if a KSK is retired (next logical step after that, but a month later), then this key is erased.

A rollover operation always ends by a signing (cf the signing command below) in order for changes and new keys to be loaded in zone.

NB : it seemed to me that a monthly renewal of ZSK was reasonable and could correspond to the majority of domains where this script could be used. Moreover the OpenBSD monthly procedure was just the good place to launch it. You can launch it less frequently with a cron task, but it has to run within the first 15 days of the month. If you don’t like it, please debate about it in comments here or in the git repository.

NB : if you wish it to be more often, code will have to be modified because of the safety function in it (blocking key generation after the 15th of the month).


Using one of these command generate keys according to the written configuration.

# ldnscript ksk

# ldnscript zsk

In order to prevent any fail, the script will refuse to create new keys after the 15th of the month. If it did, this key would be put to use before being sure it is available for validation everywhere.

The administrator is also supposed to check that the new KSK just got registered in the TLD before getting used. There is a minimum period of 15 days to do it. Should be good enough.

When generating a key, the script will echo its name, algorithm and state (generated) to a keys file in the /var/ldnscripts/zones/$ZONE/$TYPE/ sub-folder.


Signing takes care of the actual tasks of signing the zone. It must be ran after a rollover, key generation, change in the zone, as well as regularly according to the VALIDITY parameter.

There is only one argument : the complete zone name.

# ldnscript signing

It will first create a new temporary zonefile with the correct serial (ldns-signzone does not do that yet. It has to be separated.), add all keys in the zone, whatever their state is and create a signed file in /var/ldnscripts/$ZONE/signed. This file is checked for validity and if valid, copied in NSD’s chroot at /var/nsd/signed/$ZONE, then reloaded in NSD.

The VALIDITY option must be higher than the frequence you run the signing script.

I sign my zone every five days and I set my VALIDITY to 7 days :

# Cron
# dnssec signing every 5 days at 00:08
8       0 5,10,15,20,25 *       *       /var/ldnscripts/signing.cron

The signing.cron script just contains this command :


for dir in `ls /var/ldnscripts/zones/`; do
    /var/ldnscripts/ldnscript signing $dir

Once again, automation.

NB : opposite to the rollover and ZSK renewal, I thought the local administrator could have all sorts of needs or ideas about validity periods or how often those things should happen. Therefor a cron script is provided. You can simply copy-paste it to /etc/daily.local or /etc/weekly.local if it fits you.


Init … will create all the needed structure for a zone (files and keys), then sign it. It is fast and does not ask anything. Literally.

If you wish to start correctly, you need to first have the raw zone ready to be signed and its configuration, if need be, and NSD. Then you run:

# ldnscript init zone.tld

Tadam !

KSK and ZSK get generated, zone is signed and loaded in NSD. You just have to set the DS in the registry according to your registrar’s procedure.


Check will tell you whether your domain is valid or not from an external point of view.

$ ldnscript check
According to, is secure.

It will make dns requests using dig to a DNS validator randomly pick out of a list of height (Google, OpenDNS,, FreeDNS, CensurfriDNS, FDN, ARN and LDN - those are DNS validators being run by french non-profit ISP).

The zone’s statute will be displayed (secure, dns valid but insecure, bogus).

This command can also be used to check a domain that you do not run.


Status will print out all keys in the zone according to their type and state, displaying also their birth date. The zonefile currently served by NSD, as /var/nsd/signed/* will be checked and its creation date printed.

This allows you to insure everything is in order on your side. It is a complement to the check function mentionned previously.

FAQ (How do I …)

… setup a new algorithm ?

You have to change algorithm in the configuration file (global or zone) and generate a new key…

# ldnscript zsk zone.tld

… or simply wait for the next monthly rollover.

Any other option (key length…) would be the same.

… launch a KSK rollover ?

If you just want to create a new master key, just run :

# ldnscript ksk zone.tld

If you want something else, like a brand new algorithm, change the option first in the configuration then generate the key.

The key will be set to sign at the next rollover. Just set already the DS in the tld.

… use a specific key ?

You mean, another one than the one you generated lastly ? You can just generate a new one.

If you cannot, well, do a touch on the generated key that you would prefer to use. The script will use the last generated or modified key. You can also erase the keys that you don’t want to use.

… renew urgently a key now ?

The case is not really thought ! Shame on me.

You can either study the script code or read LDNS documentation (quite easy if you understand DNSSEC) or reinitialize the zone like a barbarian (which would mean erase every private and generated key and run init again).

… pass temporary or special arguments to the commands ?

You don’t. That script are written in the spirit of consistency, running every time the same exact boring sequence of actions. They are not meant to have special cases out of their configuration.

All options and arguments (out of the type of key to generate and the running domain) are written in the configuration file. If you need to change them, then you probably need to change them for the whole time (like, said, the rest of the year). So you need to write those changes.

… install it on another OS ?

I have not written the scripts with Bash or another shell. So you either have to do it yourself, or setup Korn Shell. (By the way, if you fork, please send back any improvement or idea.)

WTF ! I don’t have anymore active private key !

If you still have a retire key, change its extension to private. Those keys don’t come with expiration date yet, so they can be used anytime.

You can also use already a generated key if they are known out there.