Creating a very specific TXT only nsupdate connection for Let’s Encrypt

In the interests of maintaining Michael W Lucas in the lifestyle to which he has become accustomed, I am creating this blog post. Although Mr Lucas was the first to post, he is not solely to blame for my burdensome workload.

Jan-Piet Mens and Evan Hunt also have much to answer for. Their misdeeds include mentioning newer BIND tools which necessitated an update to an older blog post.

The worst of them all, however, is John-Mark Gurney, for he is the one who pointed out a more restrictive method granting TXT update permissions. That useful information is what has prompted me to write this blog post, early on a cold Friday winter morning. I was also told to mention Allan Jude.

Well, that, and that my Let’s Encrypt challenges failed last night because I implemented that change without any testing of the permissions.

In this post:

  • bind916-9.16.6_1
  • 12.2-RELEASE-p1

It is assumed you are familiar with Let’s Encrypt, dns-01 challenges, and BIND.


I use a centralized location to generate Let’s Encrypt certificates. The process uses DNS-01 challenge to prove I control the domains for which the certificates are being generated.

At the root of my DNS structure is a hidden DNS master (is it not listed in any domain and has no public IP addresses and is accessible only via VPN), aptly named dns-hidden-master. This server runs BIND and the named configuration grants permission for a nsupdate key to modify TXT records.

This blog post will show the changes made to the configuration so that one only specific TXT record can be modified in each domain. It will also show how I messed up and broken my certificate upgrades and how I fixed it.

The change

This change is the first non-trivial change to this infrastructure since it was implemented in mid-2017. What could go wrong?

This is the change recommedation:

@@ -204,7 +204,7 @@
        type master;
        file "zones/";
        allow-transfer { AllowZoneTransfer; };
-       update-policy  { grant zonesub TXT;
+       update-policy  { grant name TXT;
                         grant dan.dns.hidden.master    zonesub any;
                         grant        self A; };
        notify yes;

Line 5 is the original grant, and allows the key (, not shown) to update any TXT record in this domain.

Line 6 restricts this key to only the record.

I like the difference.

The consequences

As I went to bed last night, I checked my email.

Content-Type: text/plain; charset=utf-8
Message-Id: <>
Date: Fri, 18 Dec 2020 03:19:01 +0000 (UTC)

Error certs:

In total, it was all 15 certificates up for renewal that night which failed.

Checking the other certificate related email, I found:

update failed: REFUSED
[Fri Dec 18 03:18:03 UTC 2020] error updating domain
[Fri Dec 18 03:18:03 UTC 2020] Error add txt for
[Fri Dec 18 03:18:03 UTC 2020] Please check log file for more details: /var/log/
[Fri Dec 18 03:18:03 UTC 2020] Error renew

This makes me think it is DNS.

Looking in /var/log/ as instructed, I found this:

[Fri Dec 18 03:18:03 UTC 2020] adding 60 in txt "8G5oBjEfKnrgiajj6Ly83TN5Rir7sbCHGvheaHRqmyU"
[Fri Dec 18 03:18:03 UTC 2020] error updating domain
[Fri Dec 18 03:18:03 UTC 2020] Error add txt for

Next, let’s check the named logs, specifically, my /var/log/named/queries.log file.

18-Dec-2020 03:18:03.237 client @0x83185e168 (_acme- query: IN SOA -S (

It took me a while, but I soon realized my grants were too restrictive.

I’m allowing but the challenge is for

I don’t need one GRANT per zone/domain.

I need one GRANT per certificate.

Getting a list of certificates from

This sounds very straight forward.

I logged into my certs server where I run, and became the acme user.

[dan@certs:~] $ sudo su -l acme
$ bash
[acme@certs ~]$ ls certs | wc -l
[acme@certs ~]$ 

That’s 164 certificates. That’s 164 grants I need to add into my zone files…

But wait, there’s more

These certificates can have more than one host in them, for example:


I have to look inside the cert. The directory listing is not sufficient.

Here is how I started:

[acme@certs ~]$ openssl x509 -text -in ~/certs/ | grep -i dns
[acme@certs ~]$ 

Challange records will be required for host of those names.

The script

I created list-certs.

I grabbed the output into a file:

[acme@certs ~]$ ~/bin/list-certs | sort -u > certs-list
[acme@certs ~]$ 

I copied that file off to my laptop, and renamed it to certs. I wanted to create a semi-automatic way to get all the hostnames used in certificates for a given zone file.

An example:

[dan@air01:~] $ grep -i ~/Documents/certs | sort -u | xargs -n 1 -I % echo "                 grant name _acme-challenge.% TXT;"
                 grant name TXT;
                 grant name TXT;
                 grant name TXT;
                 grant name TXT;
                 grant name TXT;

Then I can copy/paste that right into the zone file.

20 minutes later, which isn’t that long when you’re adding so many records, I had completed the changes and named was restarted without error.

By that time, 11:06 PM (04:06:20 UTC), had already been run and produced errors, just like last night.

I changed the crontab and waited.

It’s running now… Waiting.

Done. Only two certs failed.

Let’s just run again and see. I checked the zone files for those two names, and it seemed fine.

Only one failed that time.

Run it again!

I think I should bump my pause time between add a TXT record and waiting.

Ahh, the last domain was an error in the grant:

-        update-policy  { grant name TXT; };
+        update-policy  { grant name TXT; };

Not sure about this

I’m not sure about this.

Each time I want a new cert, I need to first update my grants.

Some domains now have 80 new TXT records. I’m guessing the volume won’t be an issue.

Certs are public record, but now they are all conveniently available from my zone file.

What about delegation

Instead of having to add a new GRANT clause for each certificate creation, what about delegating all this? Let’s pretend I am going to use the domain for all DNS-01 challenges. How do I configure that?

Surprise! I’ve already written about this in early 2019 and I forgot about it. Please allow me to rephrase that here.

Let’s say I am requesting a new certificate for,, and, all in one cert. I will need the following CNAME records in the zone file: 600 CNAME     600 CNAME 600 CNAME

This means I have just one challenge key:

Everything points at it.

I have just one grant, this time in the zone file:

update-policy  { grant name zonesub TXT; };

I can either do:

  • lots of GRANTs


  • lots of CNAMES
  • one GRANT

Even with delegation, I still need to first add a new record to the zone file for that new hostname.

I have some more pondering to do. In that original delegation post, I talked about the various security considerations and zone / churn.

Please attribute blame to those mentioned at the top of this post.

Website Pin Facebook Twitter Myspace Friendfeed Technorati Digg Google StumbleUpon Premium Responsive

Leave a Comment

Scroll to Top