DNS over HTTPS
with BIND 9.17, Ubuntu 21.04 and LetsEncrypt
DNS over HTTPS
DNS over HTTPS (DoH) is becoming much more prevalent now. Both Google Chrome and Mozilla Firefox have supported it since 2019 and Microsoft Windows 10 21H2 is expected to introduce support for it later in 2021. With this in mind it might be time to start planning to support DNS over HTTPS if you run a BIND DNS server.
This article shows how to configure BIND 9.17 (with experimental DNS over HTTPS support) on Ubuntu 21.04 and using a LetsEncrypt certificate. Using LetsEncrypt means it’s a no-cost solution and is fully supported by most DNS over HTTPS clients – including Google Chrome, Mozilla Firefox, Microsoft Edge, and of course BIND’s own dig command.
Email address and Server name
Throughout this article I’ll use the following values for email address (for the LetsEncrypt certificate) and for the server name:
- Email address: certs@talkdns.net
- Server name: ns1.talkdns.net
Wherever you see these values below you should of course replace them with your own email address and your own server name.
Configuration Steps
We’ll go through the following steps in this article:
- Install Ubuntu Server 21.04
- Install BIND 9.17 (Development release)
- Configure a very basic named.conf.options purely to get a running BIND instance
- Test BIND to make sure it’s working at this point
- Install Certbot to manage our LetsEncrypt certificate
- Reconfigure AppArmor to allow named to access our LetsEncrypt folders
- Add the necessary lines to named.conf.options to support DNS over HTTPS
- Test DNS over HTTPS name resolution
- Setup Certbot to automatically renew the certificate and to reload named’s configuration files whenever it does so
- All done!
1. Install Ubuntu Server 21.04
Whilst the following steps may very well work with an older Ubuntu release I have specifically tested this on Ubuntu 21.04 (Server edition). You can download their ISO from here.
2. Install BIND 9.17 (Development release)
The Ubuntu 21.04 repositories include BIND 9.16, but DNS over HTTPS is currently only available in the BIND 9.17 Development release (specifically 9.17.10 or higher). In order to install BIND 9.17 we therefore need to add the ISC’s development branch repo’s:
$ sudo add-apt-repository ppa:isc/bind-dev
$ sudo apt-get update
$ sudo apt install bind9
As BIND9 installs you will see references to ppa.launchpad.net. This confirms that it’s the BIND 9.17 development release that’s being installed rather than Ubuntu’s default BIND 9.16.
Now we need to make sure BIND starts whenever the server (re)boots:
$ sudo systemctl enable named
If you’re running ufw (the Uncomplicated firewall) then you’ll also need to open the necessary ports for BIND9:
$ sudo ufw allow Bind9
$ sudo ufw allow http
$ sudo ufw allow https
We need both HTTP and HTTPS because LetsEncrypt’s Certbot runs on HTTP to validate and generate an HTTPS certificate for our BIND server.
3. Configure a very basic named.conf.options purely to get a running BIND instance
I’m going to make the assumption that your BIND server is behind a firewall on your LAN. As such it won’t be accessible to any clients other than those on your network. For the purposes of this article I’m therefore including only a very basic rudimentary named.conf.options file which will allow BIND to start successfully. You shouldn’t use this bare bones named.conf.options file on a production instance or one that’s exposed outside your LAN.
$ sudo nano /etc/bind/named.conf.options
Update the file so it has only the following rows. Alternatively if you’re a seasoned BIND administrator and you have your own standard configuration then please use it. The purpose of the following configuration is simply to prove that BIND “works” before we then start enabling DNS over HTTPS.
options {
directory "/var/cache/bind";
recursion yes;
allow-recursion { any; };
listen-on { any; };
listen-on-v6 { any; }; # you can remove this line if you don't need IPv6 support
dnssec-validation auto;
};
4. Test BIND to make sure it’s working at this point
We should now start named and make sure traditional DNS name resolution is working (over UDP and TCP port 53). First we validate our named.conf.options file:
$ sudo named-checkconf /etc/bind/named.conf.options
If that command returns no results then we’re all good – BIND has found no errors in our configuration file. So now we’ll start the named daemon and then run a quick test to make sure DNS name resolution is working correctly. We issue the systemctl restart command in case this system has been rebooted already (remember that we set named to start automatically earlier in this process):
$ sudo systemctl restart named
$ dig @ns1.talkdns.net isc.org A
If all goes well you should get the IP address of isc.org: 149.20.1.66
5. Install Certbot to manage our LetsEncrypt certificate
Now it’s time to install Certbot to manage our LetsEncrypt certificate:
$ sudo apt install certbot
Once installed we’ll use Certbot to issue our certificate. Remember to replace the email address and server name with your own of course!
$ sudo certbot certonly --standalone --preferred-challenges http --agree-tos --email certs@talkdns.net -d ns1.talkdns.net
If all goes well you should see a message similar to the following:
Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/ns1.talkdns.net/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/ns1.talkdns.net/privkey.pem
Your certificate will expire on 2021-08-28.
The contents of the “live” folder are actually symlinks to the real certificate which is stored in the “archive” subfolder of /etc/letsencrypt/
Now we need to fix the permissions on the live and archive folders so that named has permission to read these files:
$ sudo chmod 0755 /etc/letsencrypt/live
$ sudo chmod 0755 /etc/letsencrypt/archive
And then we need to change the group and permissions for the private key file
$ sudo chgrp bind /etc/letsencrypt/live/ns1.talkdns.net/privkey.pem
$ sudo chmod 0640 /etc/letsencrypt/live/ns1.talkdns.net/privkey.pem
6. Reconfigure Apparmor to allow named to access our LetsEncrypt folder
By default AppArmor will block named from accessing the certificates in the LetsEncrypt folder. Rather than modifying AppArmor’s named profile (which would be overwritten during an upgrade) we create a special AppArmor local file. These local files are a supported way of telling AppArmor that we have a slightly special configuration and they are preserved during an upgrade.
$ sudo nano /etc/apparmor.d/local/usr.sbin.named
This file should contain a single line as follows, including the comma at the end. It allows recursive read-only access to all files and folders in /etc/letsencrypt:
/etc/letsencrypt/** r,
Now we need to reload the named AppArmor profile:
$ sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.named
7. Add the necessary lines to named.conf.options to enable DNS over HTTPS
Enabling DNS over HTTPS support in BIND means editing the named.conf.options file again. We need to make two changes:
- Tell BIND where our private key and certificate are stored
- Set the appropriate listen-on statement to tell BIND to listen on port 443
$ sudo nano /etc/bind/named.conf.options
Add the following lines to the very top, above the “options {” statement:
tls local-tls {
key-file "/etc/letsencrypt/live/ns1.talkdns.net/privkey.pem";
cert-file "/etc/letsencrypt/live/ns1.talkdns.net/fullchain.pem";
};
Then add the following lines in the main options statement (e.g. right before the final “};“:
listen-on port 443 tls local-tls http default {any;};
listen-on-v6 port 443 tls local-tls http default {any;}; # you can remove this line if you don't need IPv6 support
Save the file, and then let’s check again that our named.conf.options file is OK:
$ sudo named-checkconf /etc/bind/named.conf.options
Provided all is well we now reload named so that it reads our configuration changes. We use reconfig rather than reload because we haven’t made any changes to our zone files:
$ sudo rndc reconfig
If we now quickly check the syslog we should find that BIND has loaded successfully:
$ tail /var/log/syslog
Make sure there are no errors regarding file access, the TLS cert, or AppArmor
8a. Test from the local system
We can test DNS over HTTPS from the local system by using dig and specifying a DoH query by using the +https parameter:
dig +https @ns1.talkdns.net isc.org A
If all is well then you should once again get the IP address of isc.org: 149.20.1.66. If you look closely at dig’s output you should also see something similar to the following. This confirms that the query/response operation was performed successfully over HTTPS (TCP port 443) rather than traditional UDP port 53:
;; SERVER: 51.210.161.197#443(ns1.talkdns.net) (HTTPS)
Alternatively if DNS over HTTPS isn’t working then you’ll either get an error message or dig will timeout.
8b. Test from a remote system
Ideally you should now test from a remote system as well. If you’re using ufw or any other software firewall then that will also confirm that you have the necessary ports open. If you’re running more than one name server (and if not, why not?!) then you can run dig (from BIND 9.17.11 upwards) to make an identical query as above.
9. Setup Certbot to automatically renew the certificate and to reload named’s configuration files whenever it does so
Our last step is to make sure that Certbot is setup to renew the certificate when necessary, and to reload named’s configuration whenever it does so. This steps is important because otherwise named won’t be using the correct HTTPS certificate.
$ ls /etc/cron.d
Make sure that “certbot” exists (if not then go to the Certbot homepage to read how to setup automated renewal)
Now we need to add a deploy hook to Certbot so that named reloads its configuration files whenever Certbot renews a certificate. Here again we use reconfig rather than reload because we’re not making any changes to zone files at this point. On a BIND server with many zone files it’s much quicker to simply reconfig rather than go through a full (and unnecessary) reload:
$ sudo nano /etc/letsencrypt/cli.ini
Add the following line at the end:
deploy-hook = rndc reconfig
You can test this by running:
$ sudo certbot renew --dry-run
You should see:
Dry run: slipping deploy hook command: rndc reconfig
10. All done!
That’s it! You’re all done. You now have a BIND 9.17 name server running DNS over HTTPS using an auto-renewing LetsEncrypt certificate.