LetsEncrypt BIND DNS and ACME DNS-01 server setup guide

Are you looking to setup your own DNS server for LetsEncrypt's ACME DNS-01 verification challenges then this guide is for you. LetsEncrypt wild card certificates can also be requested using the same DNS records. I use Debian Linux so this guide is based on Debian 12 at the time of this writing.

Domain registrar DNS records setup

First add a new DNS record for your dns server, for example dns.example.com AAAA 2001:0db8:a55b:42df:5d01:2359:a67e:737d or / and dns.example.com A 203.0.113.9 A/AAAA record with your server IP where you will serve your BIND9 DNS server.

Now for each hostname create a NS record in your domain registrar, for example.
NS _acme-challenge.example.com dns.example.com
NS _acme-challenge.www.example.com dns.example.com
NS _acme-challenge.server1.example.com dns.example.com
NS _acme-challenge.server2.example.com dns.example.com
NS _acme-challenge.server3.example.com dns.example.com

Server Setup

Install BIND9 DNS server apt install bind9 dnsutils

Now generate a TSIG key to update your DNS server tsig-keygen -a hmac-sha256 mainserver , I chose the key name mainserver as my primary key to get certificates for both my naked domain example.com and www.example.com. You can generate as many TSIG keys as you want for each of your servers, this is good because just in case one of your servers gets compromized then all you have to do is replace the key in your DNS server.

Now we start configuring the BIND9 server. This example includes four different servers (primary, server1, server2, server3) using the DNS server for generating LetsEncrypt certificates

/etc/bind/named.conf.local
//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

key "mainserver" {
        algorithm hmac-sha256;
        secret "RPGIkPj/1glG2PRAAn/c/7u8DXYnzQgcPAESIGsLHYk=";
};

key "server1" {
        algorithm hmac-sha256;
        secret "NaTlsbfUE6LxjoNRelPbeZQqnCZTdlL9+4csRn345p4=";
};

key "server2" {
        algorithm hmac-sha256;
        secret "mEg52zRHJn4e6DA2yh7Uyg3eOGC+UDQrvWR4PreEOao=";
};

key "server3" {
        algorithm hmac-sha256;
        secret "iUWY3cHo58mkIQ3xmtgcmnaMUGQ9/7IQ9ZRdtdXdmWY=";
};

zone "example.com" {
        type master;
        file "/var/lib/bind/example.com.zone";
        allow-query { any; };
        check-names warn;
        update-policy {
                grant example name _acme-challenge.example.com. txt;
                grant example name _acme-challenge.www.example.com. txt;
                grant server1 name _acme-challenge.server1.example.com. txt;
                grant server2 name _acme-challenge.server2.example.com. txt;
                grant server3 name _acme-challenge.server3.example.com. txt;
        };
        notify no;
};

Now configure named.conf.options file, replace the example IP with your own, for IPv4 use listen-on { 127.0.0.1; 203.0.113.9; };, I only use IPv6 so mine is set to listen-on-v6 { ::1; 2001:0db8:a55b:42df:5d01:2359:a67e:737d; };

/etc/bind/named.conf.options
options {
	directory "/var/cache/bind";

	// If there is a firewall between you and nameservers you want
	// to talk to, you may need to fix the firewall to allow multiple
	// ports to talk.  See http://www.kb.cert.org/vuls/id/800113

	// If your ISP provided one or more IP addresses for stable
	// nameservers, you probably want to use them as forwarders.
	// Uncomment the following block, and insert the addresses replacing
	// the all-0's placeholder.

	// forwarders {
	// 	0.0.0.0;
	// };

	//========================================================================
	// If BIND logs error messages about the root key being expired,
	// you will need to update your keys.  See https://www.isc.org/bind-keys
	//========================================================================
	dnssec-validation auto;
	allow-transfer {none;};
	allow-notify {none;};
	allow-recursion {none;};
	allow-query-cache {none;};
	recursion no;
	minimal-any yes;
	minimal-responses yes;

	listen-on { 127.0.0.1; 203.0.113.9; };
	listen-on-v6 { ::1; 2001:0db8:a55b:42df:5d01:2359:a67e:737d; };
};

Now create a new zone file listed above (/var/lib/bind/example.com.zone), replace the values accordingly.

Last line in this file must be a blank line.

/var/lib/bind/example.com.zone
$ORIGIN .
$TTL 0	; 0 minutes
example.com		IN SOA	dns.example.com. email.example.com. (
				100        ; serial
				3600       ; refresh (1 hour)
				3600       ; retry (1 hour)
				3600       ; expire (1 hour)
				0          ; minimum (0 seconds)
				)
$TTL 900	; 15 minutes
		IN	NS	dns.example.com.
; IP records for name servers
dns.example.com.             IN      AAAA       2001:0db8:a55b:42df:5d01:2359:a67e:737d
dns.example.com.             IN      A          203.0.113.9

Now check the zone file by running named-checkzone example.com. /var/lib/bind/example.com.zone

Now restart BIND server with your new settings systemctl restart bind9

Client server setup

Install apt install dnsutils

Now create a new file for each server you created above and paste the exact contant in the file, for example your primary client, create a new file in /root/tsig-acme.key

/root/tsig-acme.key
key "mainserver" {
        algorithm hmac-sha256;
        secret "RPGIkPj/1glG2PRAAn/c/7u8DXYnzQgcPAESIGsLHYk=";
};

For each new server paste the content of key file you created above, server1, server2, server3 and so on.

Now open the terminal in your client server and run (export) these commands.

Run these commands in client terminal
export NSUPDATE_KEY=/root/tsig-acme.key
export NSUPDATE_ZONE='example.com'
export NSUPDATE_SERVER='dns.example.com'

Now finally request the certificate using acme.sh

acme.sh
acme.sh --issue --dns dns_nsupdate -d 'example.com' -d 'www.example.com' --preferred-chain "ISRG Root X2" --keylength ec-256 --server letsencrypt

Let me know if you have any comments or if there is any error in this guide.