This is an introductory howto to get DNSSEC running with BIND >=9.9 on Debian >=8 (jessie). We assume an "clean", freshly installed bind9 here.

If you're looking for more general information about DNSSEC, you may want to have a look at:
Domain Name System Security Extensions (DNSSEC) - Wikipedia

Approach used here. We use inline-signing here, as it relieves the administrator of most of the hassle, hazards, and pitfalls of manually maintaining DNSSEC and associated Resource Records (RRs), at least once the initial configuration has been completed.

BIND version 9.9 (or later) is minimal requirement, and we presume a "pure" Debian >=8 (jessie) host system with a "clean" installation of the bind9 Debian package.
See also: Bind9.

In our examples here, for security, we'll generally be showing/using Principle of least privilege - Wikipedia. That is most important/critical here, for any and all private keys. If one wishes to be more permissive, for items other than private keys, one may wish to allow read access in the general case, however in all cases most all the files/data referenced here should be protected from general write access. That generally means group bind will need read access, and in some areas/locations/files, write access. Others should not have write access, and must not have read access to private keys.

We generally show root prompt as a leading "# ", by convention.
We generally show non-root prompt as a leading "$ ", by convention.

Initial setup

Make separate directory for keys and zones, let group bind write in zones. We also use SGID on directory, so by default items created in directory will have group ownership matching that of the directory.

# umask 077
# mkdir /etc/bind/master /var/cache/bind/keys
# chgrp bind /etc/bind/master /var/cache/bind/keys
# chmod ug=rwx,o=,g+s /etc/bind/master /var/cache/bind/keys

Serial number (& scheme) for master zone file.

For the serial, we are showing the RFC recommended format: YYYYMMDDnn While that has the advantage of the more human readable form, with inline-signing - which we're using here, it will typically rather quickly fall out-of-sync with its intended alignment to actual corresponding YYYY-MM-DD date, and may lead to confusions and incorrect presumptions. Alternatively, one may want to instead use seconds since the epoch - "Unix time" (unixtime). Notably as inline-signing can directly support unixtime, whereas with YYYYMMDDnn, the only method inline-signing can use with that is increment, which will simply increment the serial, regardless of when it is done. If one wishes to use unixtime, the GNU date utility can conveniently provide that:

$ date +%s

And likewise convert back if one wants to see the more human time, e.g.:

$ date -d @1587461621
Tue Apr 21 09:33:41 GMT 2020
$ date --iso-8601=seconds -d @1587461621

One can also use GNU date to get the YYYYMMDD portion of YYYYMMDDnn format:

$ date +'%Y%m%d'

Note one also can't arbitrarily jump serial numbers around - that can cause significant to major problems. But if we're starting new, we're relatively free to pick - so long as we do a valid number within range (both unixtime, and YYYYMMDDnn will be within range).

Now create master zone file, we show - use your actual domain, and we show YYYYMMDDnn serial format - use your actual data/scheme. Likewise for the A record, we show, for the BIND9 DNS nameserver to be useful and functional beyond the local host, you'll need to use the applicable IP address(es) for the A and/or AAAA record(s) corresponding to the NS record. Also, should be or be replaced with valid email address - replacing @ with ., and if there are any .'s before that first @ in the email address, replace those with: \. And end with: . E.g.:

would become:


So, in /etc/bind/master we create our file

$TTL    6400
@       IN      SOA     (
                      ; MNAME
               ; RNAME
                        2020042100              ; SERIAL
                        8H                      ; REFRESH
                        2H                      ; RETRY
                        1W                      ; EXPIRY
                        2H                      ; MINIMUM Negative Cache TTL
        IN      NS
ns1     IN      A

File permissions and ownership should be 640 root:bind:

# ls -ld
-rw-r----- 1 root bind 4207 Apr 21 10:01

(if you implement dynamic DNS, you'll want to then change that to bind:bind)

And in /etc/bind, add the following section to named.conf.local:

zone "" {
        type master;
        file "/etc/bind/master/";
        allow-transfer {; };

Enable and start the service:

# systemctl enable bind9.service
# systemctl start bind9.service

or if it's already running, reload:

# systemctl reload bind9.service

You may need to install dnsutils for utilities such as dig:

# apt-get -y install dnsutils

Check whether querying the zone works:

$ dig @ NS

The signing part

Keys ...

At least as of 2021-03-10:
domain key algorithm bits
.      KSK 8         2048
.      ZSK 8         2048
org.   KSK 7         2048
org.   ZSK 7         1024
net.   KSK 8         2048
net.   ZSK 8         1280
com.   KSK 8         2048
com.   ZSK 8         1280

Note that when setting up keys, notably number of bits - at least for same algorithm, picking a larger number of bits than the weakest link in ancestor chain, adds no real additional security, but does add more DNS and network load - in both sizes of data transferred, and cryptographic (CPU) overhead. Of course having smaller corresponding bit size gives one weaker security. So, for optimal performance without weakening security, one may may want to consult current bit sizes used up the ancestor chain, and also periodically review for change.

In our example here, with, we pick values that correspond with com (and .):

Create our initial keys:

# cd /var/cache/bind/keys
# dnssec-keygen -a RSASHA256 -b 2048 -f KSK
# dnssec-keygen -a RSASHA256 -b 1280

Set permissions so group bind can read the keys:

# chgrp bind*
# chmod g=r,o=*

Be sure to securely SAVE BACKUP COPIES of the keys! Once the DS record(s) is(/are) set up in the delegating parent domain/zone, if keys are lost, DNS(SEC) will be failed for a substantial period of time, and one cannot be assured of fully and immediately rectifying such situation without those same keys - any resolvers, etc. that understand and use DNSSEC may continue to reject DNS data from the domain until the DNSSEC issue is resolved - including any relevant caching and timeouts on data and keys seen, including keys seen earlier.

# cd /etc/bind

Add to the zone "" section in the file named.conf.local:

        inline-signing yes;
        auto-dnssec maintain;
        serial-update-method increment;

or, if one alternatively uses unixtime for the zone serial number (see also further above about serial numbers),
then instead use this for serial-update-method:

        serial-update-method unixtime;


     key-directory "/var/cache/bind/keys";

to the options section in named.conf.options

reload the configuration:

# rndc reload


# systemctl reload bind9.service


# rndc loadkeys
# rndc signing -nsec3param 1 0 10 auto

to let bind sign the zone.

Verify that the zone works and is properly signed and ready for DS delegation by executing (note that the example script bit requires bash (uses: <(...)) - one could alternatively do that via temporary file, or explicitly creating temporary named pipe and removing it after):

# (; k=$(printf '%05d' "$(dig @ +norecurse "$d". DNSKEY | dnssec-dsfromkey -f - "$d" | awk '{print $4;}' | sort -u)"); delv @ -a <(sed -e '/^;/d;s/[ \t]\{1,\}/ /g;s/ [0-9]\{1,\} IN DNSKEY / IN DNSKEY /;s/ IN DNSKEY / /;s/^[^ ]* [^ ]* [^ ]* [^ ]* /&"/;:s;/"[^ ]*$/b t;s/\("[^ ]*\) /\1/;b s;:t;s/$/";/;H;$!d;x;s/^\n//;s/.*/trusted-keys {\n    &\n};/' /var/cache/bind/keys/K"$d".+008+"$k".key) +root="$d" "$d". SOA +multiline)

You should see a first output line of:

; fully validated

followed by SOA record data and then signature related data.

If you get the errors "delv: No trusted keys were loaded" and/or that trusted-keys is deprecated you can verify the zone with:

# (; k=$(dig @ +norecurse "$d". DNSKEY | dnssec-dsfromkey -f - "$d" | awk '{print $4;}' | sort -u); delv @ -a <(sed -e '/^;/d;s/[ \t]\{1,\}/ /g;s/ [0-9]\{1,\} IN DNSKEY / IN DNSKEY /;s/ IN DNSKEY / /;s/^[^ ]* [^ ]* [^ ]* [^ ]* /&"/;:s;/"[^ ]*$/b t;s/\("[^ ]*\) /\1/;b s;:t;s/$/";/;H;$!d;x;s/^\n//;s/.*/trust-anchors {\n    &\n};/;s/\n\(\s\+\S\+\)/\n\1 static-key/g' /var/cache/bind/keys/K"$d".+008+"$k".key) +root="$d" "$d". SOA +multiline)

One can also do likewise pulling the DNSKEY data from DNS - but be sure to do so only over sufficiently secure trusted network communications channel (e.g.

$ (; delv @ -a <(dig @ +noall +answer "$d". DNSKEY | sed -e '/^;/d;s/[ \t]\{1,\}/ /g;s/ [0-9]\{1,\} IN DNSKEY / IN DNSKEY /;s/ IN DNSKEY / /;s/^[^ ]* [^ ]* [^ ]* [^ ]* /&"/;:s;/"[^ ]*$/b t;s/\("[^ ]*\) /\1/;b s;:t;s/$/";/;H;$!d;x;s/^\n//;s/.*/trusted-keys {\n    &\n};/') +root="$d" "$d". SOA +multiline)

It is CRUCIAL that the above be working properly before proceeding further. Also, as noted above, one should be sure one has made backup copies of the relevant keys, before proceeding further.

So, here's the part where we go fully "live" with DNSSEC. Similar to NS records for nameserver delegation, there are DS records for delegation of digital signatures/signing.

So, one needs to have the relevant DS record(s) added to the delegating parent/upstream zone. Again, be sure the validation steps noted further above have first been successfully completed before proceeding. If you proceed with the next steps without first having the above working properly, you'll significantly break your DNS, and it may take a substantial period of time to fully rectify that. (Essentially if DS data is set up in delegating parent/upstream zone, and you're not properly functional and ready for that yet, essentially you're saying that you're using DNSSEC and not to trust the data if it's not properly set up and signed with the key data provided ... and then you'd not have proper key data set up on your zone, so any DNS that honored DNSSEC would reject your DNS data, and that may persist some fair while due to key lifetimes, DNS TTLs, expiration, etc.)

So, next we obtain the needed DS data from our signed zone:

# (; dig @ +norecurse "$d". DNSKEY | dnssec-dsfromkey -f - "$d")

That gets one the DS records that need to be added to upstream/parent delegating zone to make the DNSSEC effective from the delegating authority(/ies). The precise procedure on adding those to the delegating upstream/parent will depend what exactly is there. It might be as simple as directly adding the relevant data to the DNS nameserver. For some registrars, it may be matter of doing copy/paste of the relevant data into some specific web form. Some registrars will fetch the DNSKEY data from your zone, determine the DS data from that, display it and ask for confirmation to add it (in which case, do be sure to check that it matches properly as expected).

Not covered here - alternative trust anchors - how to have DNSSEC authority for nameservers, resolvers, etc. come from source (e.g. local), directly (or indirectly), rather than directly or indirectly from the Internet root (.) DNS nameservers.


DNSSEC Resolver Test
resolver DS and signing algorithm combination tester
DNSViz - A DNS visualization tool (excellent visual DNS/DNSSEC analysis/troubleshooting)
Bind9 Bind9#BIND_9_Documentation