Using DNS for responding to ACME challenges

So, you have a service that needs a TLS certificate.

A convenient way to get a certificate is to utilize a certificate authority that supports the ACME protocol. For example Let’s Encrypt. Below, I will refer to this as the ACME server.

The ACME server has no idea who you really are. But they can verify that you are affiliated with the domains in question. During the ordering process they will issue a challenge so that you can assert that technically you control the domain names that you’re requesting a certificate for.

If your service is on the Internet and talks HTTP, the process is quite straight-forward.

The service will be reachable via a public IP address. On port 80, the ACME client will respond to requests on a URL that follows a certain pattern, and when the ACME server has verified that the URL contains the correct response data, it will mint your certificate.

This is the HTTP-01 challenge, one of two challenges defined in RFC 8555. On their website, Let’s Encrypt provides clear descriptions of the challenge types they support and those descriptions are a bit easier to digest than the RFC itself.

You can also use the TLS-ALPN-01 challenge, but for most cases, HTTP-01 is enough.

But wait a minute. What if this service is not on the Internet?

Your service might live on a private network, with an IP address such as 10.2.3.5, which is not routable on the public Internet. During the verification stage, the ACME server will not be able to connect to this service.

Or maybe your service is on the Internet, but you can’t or don’t want to open port 80. For example, your service might be an IRC server or an SFTP server.

Don’t worry. You’ll still be able to get a certificate. There’s a standardized challenge that is perfect for scenarios like these. It’s called DNS-01. The nice thing about DNS-01 is that it allows for verification out-of-band.

Using DNS for ACME verification is very flexible. Let’s have a look at how to set it up.

In this article I will not describe how to configure any specific ACME client. I will rather focus on the bigger picture and how to avoid some common pitfalls.

When you request a certificate using DNS-01, the ACME server will look for a special DNS record that holds the challenge response.

Let’s say you want to request a certificate for intranet.contoso.com

The ACME server will then look for a TXT record at _acme-challenge.intranet.contoso.com

In the standard case, your ACME client (or an associated script) goes to the DNS server and instructs it create a new TXT record with the name _acme-challenge.intranet.contoso.com and a certain “magic” value.

From RFC 8555:

A client fulfills this challenge by constructing a key authorization from the “token” value provided in the challenge and the client’s account key. The client then computes the SHA-256 digest [FIPS180-4] of the key authorization.

Since you will need to create such records every time you request or renew a certificate, I can warmly recommend you to use a DNS service that offers an API. You could use Google Cloud, AWS or Cloudflare. Those are just examples though, there are many others.

Another benefit with DNS-01 is that it enables you to request wildcard certificates. With Let’s Encrypt, it’s the only challenge type that may be used for that case.

But what if it’s not you/your team that controls DNS for contoso.com?

Perhaps it’s controlled by another team or even another department. They might not be willing to give you control of DNS for the whole domain.

Luckily, there are ways to make arrangements for that. Enter CNAME records. If you’re not familiar with those, I recommend you to have a look at the Wikipedia article for CNAME records. Simply put, CNAME records are records that points to other records. Like symbolic links in Unix.

So instead of creating the TXT record directly at _acme-challenge.intranet.contoso.com you can set up a structure like this:

You create a dedicated domain just for responding to ACME challenges. Let’s call it contoso-acme-responses.com

The department that controls contoso.com sets up a CNAME record:

_acme-challenge.intranet.contoso.comintranet.contoso.com.contoso-acme-responses.com

Now, when your client is responding to the challenge you will not ask it to create a TXT record at _acme-challenge.intranet.contoso.com but instead at intranet.contoso.com.contoso-acme-responses.com

When you’re using a DNS service with a decent API, creating a new record is easy as pie. The same is true for removing records. In the client’s clean-up phase, it should remove the TXT record, regardless of whether the order succeeded or not.

If you don’t want to, you don’t have to register a separate domain for this. You could also create a new zone instead. For example acme-challenges.contoso.com

If you opt for this solution, then the department that controls contoso.com needs to delegate this zone to you by creating a NS record.

The TXT record you would create in this example would be intranet.contoso.com.acme-challenges.contoso.com

For reference, here’s an example of what it looks like if acme-challenges.contoso.com has been delegated to Google Cloud’s DNS service:

; <<>> DiG 9.10.6 <<>> acme-challenges.contoso.com NS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47111
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;acme-challenges.contoso.com.              IN      NS

;; ANSWER SECTION:
acme-challenges.contoso.com.       21600   IN      NS      ns-cloud-a4.googledomains.com.
acme-challenges.contoso.com.       21600   IN      NS      ns-cloud-a1.googledomains.com.
acme-challenges.contoso.com.       21600   IN      NS      ns-cloud-a3.googledomains.com.
acme-challenges.contoso.com.       21600   IN      NS      ns-cloud-a2.googledomains.com.

;; Query time: 64 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 19 18:59:19 CEST 2025
;; MSG SIZE  rcvd: 166

The start of authority record would look something like this:

; <<>> DiG 9.10.6 <<>> acme-challenges.contoso.com SOA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10796
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;acme-challenges.contoso.com.              IN      SOA

;; ANSWER SECTION:
acme-challenges.contoso.com.       21600   IN      SOA     ns-cloud-a1.googledomains.com. cloud-dns-hostmaster.google.com. 127 21600 3600 259200 300

;; Query time: 90 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 19 12:46:05 CEST 2025
;; MSG SIZE  rcvd: 138

The downside of using the CNAME-based approach is that, for every domain you want to use, the admins will need to create the corresponding CNAME record before you can request the certificate. If you frequently introduce new hostnames, this might be impractical. In that case, you might want to consider using wildcard certificates.

To conclude:

In this article we took a look at how the DNS-01 challenge can be used for verifying that you’re authorized to request certificates for a certain domain or subdomain. Even if you have a service on a private network, that’s not talking HTTP and you don’t fully control the DNS for the domain, you can still fetch a certificate. Ain’t that swell?

Thanks to Kristoffer Grönlund and Erik Thorsell for reading drafts of this article.

Author: Carl Winbäck
Published: 2025-09-21

Back to the main page