From bc65da09447cae75f1dd63829a01bac77671ab04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81ro=CC=82me=20Billiras?= Date: Fri, 25 Sep 2026 23:37:28 +0200 Subject: [PATCH] BUG/MEDIUM: acme: pass record names relative to the zone to libdns The dns-01 solver builds the TXT record name from the full domain (`_acme-challenge.sub.example.com`) and hands it as-is to the libdns provider, together with the zone (`example.com.`). libdns expects record names to be relative to the zone. Providers that map the name directly onto a subdomain field, like OVH, end up creating `_acme-challenge.sub.example.com.example.com`, so the propagation check never succeeds and the challenge times out. Others, like Cloudflare, happen to work because their API normalizes a name that already ends with the zone. Use `libdns.RelativeName()` in both `Present()` and `CleanUp()` once the zone is known. The propagation check in `Wait()` builds its own absolute name and is not affected. --- acme/dns01.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/acme/dns01.go b/acme/dns01.go index 655598be..0bb29b10 100644 --- a/acme/dns01.go +++ b/acme/dns01.go @@ -82,6 +82,8 @@ func (s *DNS01Solver) Present(ctx context.Context, domain, zone, keyAuth string) } else { zone = rooted(zone) } + // libdns expects record names relative to the zone. + rec.Name = libdns.RelativeName(rec.Name, zone) results, err := s.provider.SetRecords(ctx, zone, []libdns.Record{rec}) if err != nil { @@ -160,6 +162,8 @@ func (s *DNS01Solver) CleanUp(ctx context.Context, domain, zone, keyAuth string) } else { zone = rooted(zone) } + // libdns expects record names relative to the zone. + rr.Name = libdns.RelativeName(rr.Name, zone) _, err := s.provider.DeleteRecords(ctx, zone, []libdns.Record{rr}) if err != nil {