DNS Hosting Guide: Hidden Master with DNSSEC
Manage your own DNS using BIND in a hidden master configuration.
DNS is one of the few things I don't like to host myself. A DNS server running on a single host will cause slow queries for far-away clients, making your site seem less responsive. In addition, a failed DNS lookup is a much more serious problem than a web service being down. If your mail server isn't responding, most other mail servers will queue all your incoming mail to be retried later. But if the DNS lookup for your MX record fails, they will usually reject the message altogether.
Because of these issues, I have always used third-party DNS providers. Most professional DNS services are inexpensive for personal use, resistant to DDOS attacks and downtime, and support low-latency queries from anywhere in the world using anycast routing. However, using a paid DNS provider usually forces you to manage all your domains and subdomains through a clunky web interface. Until now!
This guide describes how to run the BIND DNS server in a hidden master configuration. The idea is that you run BIND locally, where you can easily edit your plain text zone file or automate your domain configuration, but use a secondary, more resilient DNS provider for all the actual query resolution. Your local BIND server will act as the primary (or master) DNS server, and will automatically notify your secondary DNS servers when any changes are made. The trick is that when setting your primary nameservers in your domain's registrar, you only use the IP addresses of your secondary nameservers. This way, you can easily manage your zone file locally, but none of the actual query traffic goes to your box.
I use DNS Made Easy for my secondary DNS service. Their small business
plan is about $30 per year, and that gets you support for 10 domain names, 5 million queries per month, and DNSSEC.
I've found it more than adequate for a personal site, but there are other options as well—Dyn
comes to mind (but recently acquired by Oracle...buyer beware).
As always, this guide assumes you're running FreeBSD. The instructions should map pretty closely to your Linux
distro of choice—just install bind from your package manager and modify the file paths
where appropriate.
EDIT (26 Nov 2017): These issues were actually caused by a problematic loader.conf optimization I had configured.
You can use BIND 9.11 without issue. Just make sure the net.inet.tcp.soreceive_stream tunable is set to 0.
See this forum post for more information.
I always make sure to build with the IPV6- and DNSSEC-related options. Then, enable bind
to start on boot (the daemon is called named):
Now you are ready to edit the configuration file. The annotated example below assumes you are hosting a domain
called example.com. Most of the IP addresses in the example are fake. You will need
to substitute your own domain and relevant IP addresses where appropriate. Your secondary DNS provider should
provide you with the IP addresses for the zone transfers, as well as those of your public nameservers.
Now you need to write a zone file for your domain—this file contains the actual DNS records.
If you want to configure DNSSEC for your domain, you'll need to generate some keys. Make sure your secondary
DNS provider supports DNSSEC first (I know that DNS Made Easy does).
I use the ECDSA algorithm when generating keys, since they are smaller and more computationally efficient.
However, if you're concerned about maximum compatibility with other DNS resolvers, you probably want to stick with
RSA—just replace ECDSAP256SHA256 with RSASHA256 below.
ECDSA is good enough for Cloudflare though, so
I'm sticking with it for this example.
First, generate a Zone Signing Key (ZSK) for your domain by running the following:
This will generate a keypair for the example.com in BIND's key directory.
Next, generate a Key Signing Key (KSK) for the domain in a similar fashion:
Take note of the KSK's generated filename—you'll need it in a bit. You should now have 4 files for this domain
in BIND's key directory: one pair for the ZSK, and another pair for the KSK. We will come back to these at the end of
the guide when you configure DNS settings at your registrar.
Now we come to the fun part: writing your zone file! This is a plain text file where you'll specify all the DNS records
for your domain. The below example uses fake IP addresses for a domain named example.com. I've
included some commentary to help you out, but basically you'll just be translating the existing records you configured in your original
DNS provider's web portal.
At this point, we're done configuring BIND. The next step is to ensure your secondary DNS provider can connect
to your server to perform zone transfers. You'll need to configure your server as the primary DNS server in your
secondary DNS provider's web portal. With DNS Made Easy, this page is found in the Advanced menu under
Secondary IP Sets. Specify your server's public IP address here.
If you have a firewall in place, you'll need to allow TCP and UDP traffic over port 53. If you used my FreeBSD Server Guide
to configure the PF firewall, you can just add domain to the inbound_tcp_services
and inbound_udp_services variables. Be sure to reload PF's ruleset:
Now, the moment of truth. It's time to start BIND and test your new DNS server!
Check /var/log/messages to ensure BIND started up properly. Hopefully you didn't make any typos.
You can verify BIND is working by doing a simple DNS query:
Also, make sure BIND is correctly serving the domain you configured in your zone file:
Now, head to your secondary DNS provider's web portal. When BIND started up, it should have read your zone file and sent a
NOTIFY to your secondary DNS servers, informing them to do a zone transfer from your hidden master.
If that happened successfully, your provider's web portal should show the DNS servers as in sync, with the current serial numbers
matching.
If that didn't happen, or you see a problem, increment the serial number in your zone file and give BIND a reload:
This should re-read your zone file and update your secondary servers. Check for any suspicious messages in your log files.
Once you have zone transfers working, and your primary and secondary nameservers are in sync, you're ready to officially
change your public nameservers at your domain's registrar.
Your registrar has the secret sauce that tells other DNS servers which nameservers to query for information
about your domain. Once you've verified everything is working, you can switch your nameservers over to your secondary
DNS provider. You probably want to do this late at night, or during a time when you don't expect much traffic to your
site. I use Namecheap, but the instructions should carry over to
most other registrars.
In your registrar's web portal, your domain's settings page should have an option to set your nameservers. At Namecheap,
you want to select Custom DNS. You will then need to provide the fully qualified domain name of your secondary
DNS provider's public DNS servers. As DNS Made Easy states at the top of their portal: These are the name servers that you will want to add as NS records in your zone and also assign to your domain at your domain registrar.
If you'd like your public nameservers for example.com to look like ns1.example.com
instead of ns1.yourdnsprovider.com, then you can configure "vanity nameservers" for your domain.
First, make sure you have A records (and probably AAAA records) for your secondary DNS provider's public name servers in your zone file (I emphasized this in
the example zone file above).
I can't speak for other registrars, but at Namecheap, you can go to the Advanced DNS page for your domain and
scroll down to Personal DNS Server. Then you can add ns1, ns2...
and map them to the IP addresses of your secondary DNS provider's public nameservers.
Then, in the Custom DNS field, you can just put ns1.example.com, ns2.example.com, etc.
Doing this creates a "glue record" at your registrar for your domain's DNS servers. You can Google this if you're interested in
the details. Also, the web interface at Namecheap currently only allows IPv4 glue records. I had to open a support ticket to
have IPv6 glue records added for DNS Made Easy's public IPv6 servers.
If you configured DNSSEC above, there is one last step to complete while you're on your registrar's web portal. You'll
need to provide the SHA-1 and SHA-256 digests of the KSK you generated to your registrar. Your registrar will then add some
fancy records for you so that other resolvers can cryptographically verify your DNS records. (Sorry for the hand-wavy
explanation—it's 1:00 AM on a Friday night and I'm tired.)
Remember the filename I asked you to remember from the dnssec-keygen command above? We'll
use it now. You want the file containing the Key Signing Key (not the Zone Signing Key). It's annoying, because the filenames
look exactly the same except for a four-digit identifier at the end. It's easy to figure out though—the contents of the
.key file will tell you which one it is.
Once you've found the correct file, run the following command:
You will use this output to populate the DS records for your domain at your registrar. At Namecheap, this is located
under Advanced DNS→DNSSEC. The first column is the key tag, which corresponds to the key's file name.
The second column is the encryption type. If you used ECDSA, this will be 13. If you went with RSA, you'll use 8 here.
The third column is the digest type: 1 for SHA-1 and 2 for SHA-256. The final column contains the actual digest.
These columns correspond to the four fields for DS records in the Namecheap web portal. Copy and paste the appropriate values
there. Other registrars should have a similar process.
It will take an hour or so for your DNS updates to propagate. You can verify DNSSEC is working by by querying a different
DNS server (the below example uses Google's public DNS).
If you see the ad flag, then your DNSSEC was validated successfully. You can also check
your DNSSEC status on Verisign's test page.
Once you've gotten all this working, you might consider setting the default nameserver on your server to your local BIND
instance. In addition to using no network overhead, you'll also take advantage of BIND's default query cache. All it takes
is a simple edit to your resolv.conf:
If you perform DNS updates frequently, I think you'll find the hidden master setup an invaluable productivity boost for any
domains you control. Remember: to update your zone, just make the necessary record changes in your zone file, increment the serial,
and issue a service named reload.
Installing BIND
On FreeBSD, install bind99 from ports. There are more recent versions available, but in
my experience they all had bugs in their rc scripts and weird runtime issues. 9.9 is
the extended support release, and it's the only one that I can confirm works 100% on FreeBSD 11. Use other versions
at your own risk.
cd /usr/ports/dns/bind911
make install clean
Optional: Configuring DNSSEC
dnssec-keygen -a ECDSAP256SHA256 -K /usr/local/etc/namedb/keys -n ZONE example.com
Generating key pair.
Kexample.com.+013+29679
dnssec-keygen -f KSK -a ECDSAP256SHA256 -K /usr/local/etc/namedb/keys -n ZONE example.com
Generating key pair.
Kexample.com.+013+15315
Writing a Zone File
Configuring Zone Transfers
pfctl -f /etc/pf.conf
service named start
dig @127.0.0.1 +short google.com
172.217.5.206
dig @127.0.0.1 +short example.com
203.0.113.41
service named reload
Notifying Your Registrar
Side Note: Vanity Nameservers
Optional: DNSSEC at the Registrar
dnssec-dsfromkey /usr/local/etc/namedb/keys/Kexample.com.+013+15315.key | awk '{print $4, $5, $6, $7}'
15315 13 1 7F5ED13547D9860965437D0F7CB6BFA7C70F1F62
15315 13 2 AD0C012F749F0422E0CC88D11B65248E78945F149572D54C8AE7C5B63C30E621
dig @8.8.8.8 example.com +dnssec | grep -m1 flags:
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
Conclusion