Remix.run Logo
clvx 2 days ago

But you have to have your dns api key loaded and many dns providers don’t allow api keys per zone. I do like it but a compromise could be awful.

qwertox 2 days ago | parent | next [-]

You can make the NS record for the _acme-challenge.domain.tld point to another server which is under your control, that way you don't have to update the zone through your DNS hoster. That server then only needs to be able to resolve the challenges for those who query.

jacooper 2 days ago | parent [-]

How?

andreashaerter 2 days ago | parent | next [-]

CNAMEs. I do this for everything. Example:

1. Your main domain is important.example.com with provider A. No DNS API token for security.

2. Your throwaway domain in a dedicated account with DNS API is example.net with provider B and a DNS API token in your ACME client

3. You create _acme-challenge.important.example.com not as TXT via API but permanent as CNAME to _acme-challenge.example.net or _acme-challenge.important.example.com.example.net

4. Your ACME client writes the challenge responses for important.example.com into a TXT at the unimportant _acme-challenge.example.net and has only API access to provider B. If this gets hacked and example.net lost you change the CNAMES and use a new domain whatever.tld as CNAME target.

acme.sh supports this (see https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mo... this also works for wildcards as described there), most ACME clients do.

I also wrote an acme.sh Ansible role supporting this: https://github.com/foundata/ansible-collection-acmesh/tree/m.... Example values:

  [...]
  # certificate: "foo.example.com" with an additional "bar.example.com" SAN
  - domains:
    - name: "foo.example.com"
      challenge:  # parameters depend on type
        type: "dns"
        dns_provider: "dns_hetzner"
        # CNAME _acme-challenge.foo.example.com => _acme-challenge.foo.example.com.example.net
        challenge_alias: "foo.example.com.example.net"
    - name: "bar.example.com"
      challenge:
        type: "dns"
        dns_provider: "dns_inwx"
        # CNAME _acme-challenge.bar.example.com => _acme-challenge.example.net
        challenge_alias: "example.net"
  [...]
theschmed 2 days ago | parent | next [-]

Thank you for this clear explanation.

teruakohatu 2 days ago | parent | prev [-]

This has blown my mind. Its been a constant source of frustration since Cloudflare stubbornly refuses to allow non-enterprise accounts to have a seperate key per zone. The thread requesting it is a masterclass in passive aggressiveness:

https://community.cloudflare.com/t/restrict-scope-api-tokens...

Jnr 2 days ago | parent | next [-]

When setting up the API key, use the "Select zones to include or exclude." section. Works fine on the free account.

teruakohatu 2 days ago | parent [-]

I should have clarified, you can’t for subdomains on a non-enterprise account.

Kovah 2 days ago | parent | prev [-]

Could you elaborate on the separate key per zone issue? It's possible to create different API keys which have only access to a specific zone, and I'm a non-enterprise user.

johnmaguire 2 days ago | parent [-]

This allows you to restrict it to a domain (e.g. example.com) but not a sub-domain of that domain.

Kovah 2 days ago | parent [-]

Ah I see, thanks for the clarification!

bwann 2 days ago | parent | prev | next [-]

I used the acme-dns server (https://github.com/joohoi/acme-dns) for this. It's basically a mini DNS server with a very basic API backed with sqlite. All of my acme.sh instances talk to it to publish TXT records, and accepts queries from the internet for those TXT records.

There's a NS record so *.acme-dns.example.com delegates requests to it, so each of my hosts that need a cert have a public CNAME like _acme-challenge.www.example.com CNAME asdfasf.acme-dns.example.com which points back to the acme-dns server.

When setting up a new hostname/certificate, a REST request is sent to acme-dns to register a new username/password/subdomain which is fed to acme.sh. Then every time acme.sh needs to issue/renew the certificate it sends the TXT info to the internal acme-dns server, which in turn makes it available to the world.

dwood_dev 2 days ago | parent | prev | next [-]

Usually you just CNAME it.

You can cname _acme-challenge.foo.com to foo.bar.com.

Now, if when you do the DNS challenge, you make a TXT at foo.bar.com with the challenge response, through CNAME redirection, the TXT record is picked up as if it were directly at _acme-challenge.foo.com. You can now issue wildcard certs for anything for foo.com.

I have it on my backlog to build an automated solution to this later this year to handle this for hundreds of individual domains and then put the resulting certificates in AWS secrets manager.

I'm going to also see if I can make some sort of ACME proxy, so internal clients authenticate to me, but they cant control dns, so I make the requests on their behalf. We need to get prepared for ACME everywhere. In May 2026, its 200 day certs, it only goes down from there.

qwertox 2 days ago | parent | prev | next [-]

In my case I have a very small nameserver at ns.example.com. So I set the NS record for _acme-challenge.example.com to ns.example.com.

An A-record lookup for ns.example.com resolves to the IP of my server.

This server listens on port 53. It is a custom, small Python server using `dnslib`, which also listens on port let's say 8053 for incoming HTTPS connections.

In certbot I have a custom handler, which, when it is passed the challenge for the domain verification, sends the challenge information via HTTPS to ns.example.com:8053/certbot/cache. The small DNS-server then stores it and waits for a DNS query on port 53 for that challenge to come in, and if it does, it serves it that challenge's TXT record.

  elif qtype == 'TXT':
    if qname.lower().startswith('_acme-challenge.'):
      domain = qname[len('_acme-challenge.'):].strip('.').lower()
      if domain in storage['domains']:
        for verification_code in storage['domains'][domain.lower()]:
          a.add_answer(*dnslib.RR.fromZone(qname + " 30 IN TXT " + verification_code))
The certbot hook looks like this

   #!/usr/bin/env python3
   
   import ...

   r = requests.get('https://ns.example.com:8053/certbot/cache?domain='+urllib.parse.quote(os.environ['CERTBOT_DOMAIN'])+'&validation-code='+urllib.parse.quote(os.environ['CERTBOT_VALIDATION']))
That one nameserver-instance and hook can be used for any domain and certificate, so it is not just limited to the example.com-domain, but can also deal with challenges for let's say a *.testing.other-example.com wildcard certificate.

And since it already is a nameserver, it might as well serve the A records for dev1.testing.other-example.com, if you've set the NS record for testing.other-example.com to ns.example.com.

cherry_tree 2 days ago | parent | prev [-]

https://cert-manager.io/docs/configuration/acme/dns01/#deleg...

yupyupyups 2 days ago | parent | prev | next [-]

It's time for DNS providers to start supporting TSIG + key management. This is a standardized way to manipulate DNS records, and has a very granular ACL.

We don't need 100s of custom APIs.

https://en.m.wikipedia.org/wiki/TSIG

reactordev 2 days ago | parent [-]

The whole point is to abstract that from the users so they don’t know it’s a giant flat file. Selling a line at a time for $29.99. (I joke, obviously)

withinboredom 2 days ago | parent [-]

Digital Ocean DNS is free (it’s the only reason I have an account there)

immibis 2 days ago | parent | prev | next [-]

General note: your DNS provider can be different from your registrar, even though most registrars are also providers, and you can be your own DNS provider. The registrar is who gets the domain name under your control, and the provider is who hosts the nameserver with your DNS records on it.

qwertox 2 days ago | parent [-]

Yes, and you can be your own DNS provider only for the challenges, everything else can stay at your original DNS provider.

bananapub 2 days ago | parent | prev | next [-]

no you don't, you can just run https://github.com/joohoi/acme-dns anywhere, and then CNAME _acme_challenge.realdomain.com to aklsfdsdl239072109387219038712.acme-dns.anywhere.com. then your ACME client just talks to the ACME DNS api, which let's it do nothing at all aside from deal with challenges for that one long random domain.

Arnavion 2 days ago | parent | next [-]

You can do it with an NS record, ie _acme_challenge.realdomain.com pointing to the DNS server that you can program to serve the challenge response. No need to make a CNAME and involve an additional domain in the middle.

aflukasz 2 days ago | parent [-]

Yeah, but then you can just as well use http-01 with like same effort.

gruez 2 days ago | parent [-]

no, because dns supports wildcard certificates, unlike http.

cpach 2 days ago | parent | next [-]

dns-01 is also good for services on a private network.

aflukasz 2 days ago | parent | prev [-]

Ah, good point.

8organicbits 2 days ago | parent | prev | next [-]

There's a SaaS version as well, if you don't want to self-host.

https://docs.certifytheweb.com/docs/dns/providers/certifydns...

rglullis 2 days ago | parent | prev [-]

I've been hoping to get ACME challenge delegation on traefik working for years already. The documentation says it supports it, but it simply fails every time.

If you have any idea how this tool would work on a docker swarm cluster, I'm all ears.

grim_io 2 days ago | parent | prev | next [-]

Sounds like a DNS provider problem. Why would Nginx feel the need to compromise because of some 3rd party implementation detail?

toomuchtodo 2 days ago | parent [-]

Because users would pick an alternative solution that meets their needs when they don't have leverage or ability to change DNS provider. Have to meet users where they are when they have options.

UltraSane 2 days ago | parent | prev | next [-]

This concerned me greatly so I use AWS Route53 for DNS and use an IAM policy that only allows the key to work from specific IP addresses and limit it to only create and delete TXT records for a specific record set. I love when I can create exactly the permissions I want.

AWS IAM can be a huge pain but it can also solve a lot of problems.

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_p...

https://repost.aws/questions/QU-HJgT3V0TzSlizZ7rVT4mQ/how-do...

https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/sp...

ddtaylor 2 days ago | parent | prev | next [-]

It's a bit of a pain in the ass, but you can actually just publish the DNS records yourself. It's clear they are on the way out though as I believe it's only a 30 day valid certificate or something.

I use this for my Jellyfin server at home so that anyone can just type in blah.foo regardless of if their device supports anything like mDNS, as half the devices claim to support it but do not correctly.

fmajid 2 days ago | parent | prev | next [-]

My company's DNS provider doesn't even have an API so I delegated to a subdomain, hosted it on PowerDNS, and used Lego to automate the ACME.

quicksilver03 2 days ago | parent | prev | next [-]

Is having one key per zone worth paying money for? It's on the list of features I'd like to implement for PTRDNS because it makes sense for my own use case, but I don't know if there's enough interest to make it jump to the top of this list.

hashworks 2 days ago | parent | prev | next [-]

If you host a hidden primary yourself you get that easily.

Sesse__ 2 days ago | parent [-]

Many DNS providers also don't support having an external primary.

alanpearce 2 days ago | parent | next [-]

Hurricane Electric support a hidden primary as part of their free DNS nameserver service (do you actually want to expose your primary when someone else can handle the traffic?)

https://dns.he.net

Sesse__ 2 days ago | parent [-]

Yup, but it's a bit of a dance for bootstrapping, since they require you to already have delegated to them, but some TLDs require all NSes to be in sync and answer for the domain before delegating…

nulbyte 2 days ago | parent | prev | next [-]

Do most of them let you add an NS record?

qwertox 2 days ago | parent [-]

And if they don't, you might consider switching to Cloudflare for DNS hosting.

rfmoz 2 days ago | parent | prev [-]

Give a try to DNSMadeEasy or RcodeZero

xiconfjs 2 days ago | parent | prev [-]

if even PowerDNS doesn‘t support it :(

tok1 2 days ago | parent [-]

True for API but you can do DynDNS updates (RFC 2136), TSIG-authenticated on a per-zone basis. [1]

Can even be controlled quite granularly with a Lua-based updatepolicy, if you want e.g. restricting to only the ACME TXT records. [2]

[1] https://doc.powerdns.com/authoritative/dnsupdate.html

[2] https://github.com/PowerDNS/pdns/wiki/Lua-Examples-(Authorit...