I have been running the DNS for most of my domains myself for a number of years now. While you may have any number of reasons for self-hosting DNS, my chief motivator was the ease of implementing the ACME DNS-01 challenge when requesting certificates. This in turn allows me to request and use publicly verifiable certificates for devices and applications that are only accessible from my internal network. As I have some services that I want to be accessible both publicly and internally but on different IPs, I use a split-horizon DNS setup.

In a split-horizon DNS setup, you have separate zonefiles for the internal and external ‘view’ of a given DNS zone. In the external view you would have records for publicly available services like this:

$ORIGIN demonet.benno.frl.
webserver       AAAA    2001:db8:1337::cafe:443
                A       192.0.2.42

The internal records would then look something like this:

$ORIGIN demonet.benno.frl.
webserver       AAAA    fd01:db8:1337::cafe:443
                A       192.168.2.42
internalonly    AAAA    fd01:db8:1337::dead:80
                A       192.168.2.24

In these examples demonet.benno.frl is the split zone. Note that webserver has public IPs in the external view and internal IPs in the internal view. The internal view additionally has a record for a service that is only intended to be accessed internally.

With the nameserver appropriately configured, the setup described above works very well, and I have used it a such for a number of years. It pained me however, that my split-zones did not have DNSSEC signing set up. Most my split-horizon zones are subdomains of a domain that has a third-party DNS provider and where the subdomains are delegated to my own nameservers or to those of Hurricane Electric in a hidden-master setup. The DNS provider for the main domain however did not support specifying DS1 records for these delegated zones, preventing me from setting up DNSSEC in a functional and meaningful manner. The other factor was that I feared DNSSEC with split-horizon zones to be hugely complicated and messy, so I never really looked into it.

Recently however, I noticed my DNS provider now did support DS records, driving me to look into signing my split zones. I was surprised to find that it was extremely easy, at least for my existing setup.

My master nameserver is the same for both internal and external DNS. This means I can sign both the external and the internal zone with the same DNSSEC keys without having to shuffle keys around or generate additional keys. Below is everything that is needed to setup DNSSEC for a split-horizon DNS zone.

First, I generate the KSK2:

➜  dnssec-keygen -f KSK -3 -a ECDSAP256SHA256 colo.bajansen.nl
Generating key pair.
Kcolo.bajansen.nl.+013+16330

Followed by the ZSK3:

➜  dnssec-keygen -3 -a ECDSAP256SHA256 colo.bajansen.nl
Generating key pair.
Kcolo.bajansen.nl.+013+35753

Next, we need to generate a DS record based on the KSK public key:

➜  dnssec-dsfromkey Kcolo.bajansen.nl.+013+16330.key
colo.bajansen.nl. IN DS 16330 13 2 CC53C848AE71682066584CA829D31CCAD4121F62082EA6FB954A3A294A08BFEE

This DS record is what we’ll have to register with the DNS provider so our signed delegated zone can be verified.

Next is the magic part, where we have BIND do all of the complicated stuff such as actually signing the zones.

zone "colo.bajansen.nl" {
	type master;
	auto-dnssec maintain;
	key-directory "/usr/local/etc/namedb/keys";
	inline-signing yes;
	file "/usr/local/etc/namedb/master/colo.bajansen.nl.zone";
};

Here we set up BIND to automatically maintain DNSSEC for the zone, handling stuff such as (re)signing and key management. Note that you’ll have to add this DNSSEC configuration for both the external and the internal view, or you’ll only have one of the views signed.

Having set all this up and with BIND reloaded, your zones should now be signed. This is also the point where you can add the DS record with your upstream DNS provider without breaking anything.

Note that while we have specifically generated our DNSSEC keys with support for NSEC3 (the -3 flag), we have at this point not yet provided an NSEC3PARAM with a salt, meaning our zone is currently effectively using NSEC, allowing zone traversal. Fortunately, this can also be automatically handled by BIND through the following command:

rndc signing -nsec3param 1 0 10 $(head -c 300 /dev/random | shasum | cut -b 1-16) colo.bajansen.nl. IN external-view

Instead of the subshell thing you can also specify a salt generated elsewhere. Note that you’ll also have to run this command for the internal view if you want this to use NSEC3 as well.


  1. Delegation signer: This is a record ‘above’ the delegated zone and serves to verify the validity of the delegated zone’s DNSSEC signing. ↩︎

  2. Key-signing key ↩︎

  3. Zone-signing key ↩︎