#8168·infisical

fix(pki): Azure DNS ACME challenge TXT record always written at zone apex instead of per-identifier name

Author: jkritzenCreated Sep 16, 2026Updated Sep 16, 2026

Bug Report: External ACME CA — Azure DNS writes challenge TXT at zone apex instead of per-identifier name

Summary

When using an external ACME-compatible CA (e.g. Let's Encrypt) with Azure DNS as the DNS-01 provider, Infisical always creates the challenge TXT record at the zone apex (_acme-challenge relative to the configured zone) instead of at the per-identifier name _acme-challenge.<identifier>. Domain validation fails unless the requested certificate's identifier happens to be exactly the zone name.

Affected code

backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts, function getAcmeChallengeRecord:

typescript
if (provider === AcmeDnsProvider.DNSMadeEasy || provider === AcmeDnsProvider.AzureDNS) {
  // For DNS Made Easy, we don't need to provide the domain name in the record name.
  recordName = "_acme-challenge";
} else {
  recordName = `_acme-challenge.${identifierValue}`;
}

if (provider !== AcmeDnsProvider.DNSMadeEasy) {
  recordName = await resolveAcmeChallengeCname(recordName);
}

Problems for Azure DNS:

  1. recordName = "_acme-challenge" omits the identifier entirely, so the record is written to <zone>/TXT/_acme-challenge regardless of which host is being validated.
  2. The CNAME resolution afterwards operates on the bare _acme-challenge label, which can never resolve, so the documented CNAME-delegation workflow (https://infisical.com/docs/documentation/platform/pki/ca/acme-ca, FAQ "How do I use DNS CNAME delegation for ACME challenges?") cannot work with Azure DNS.
  3. Because the record set is fixed at the apex, concurrent orders and multi-SAN orders collide: a second challenge overwrites the first (single PUT per record set), so only one identifier can ever validate.

Note: DNS Made Easy's API apparently expects zone-relative record names via a separate mechanism, but the Azure DNS provider implementation (dns-providers/azure-dns.ts) uses PUT .../dnsZones/{zone}/TXT/{recordName} — the record name must be relative to the zone and contain the identifier, otherwise the ACME CA (which queries _acme-challenge.<identifier>) cannot find the record.

Reproduction

  1. Create an external ACME CA (Let's Encrypt) with provider azure-dns and hosted zone /subscriptions/.../dnsZones/acme.example.com.
  2. Create a CNAME in the domain's parent zone: _acme-challenge.host.example.com → _acme-challenge.host.acme.example.com.
  3. Request a certificate for host.example.com.
  4. Observed: TXT record created at _acme-challenge.acme.example.com (zone apex), the upstream CA authorization for host.example.com fails with DNS problem.

Expected behavior

For Azure DNS the TXT record should be created at the record name derived from the identifier (_acme-challenge.<identifierValue>), with the zone name stripped to make it relative to the configured zone, and after following any CNAME chain (resolveAcmeChallengeCname on the full record name). Then:

  • per-host certificates work,
  • the documented CNAME-delegation workflow works,
  • concurrent/multi-SAN orders write to distinct record sets.

Suggested fix

typescript
if (provider === AcmeDnsProvider.DNSMadeEasy) {
  recordName = "_acme-challenge"; // unchanged, provider API is zone-relative
} else {
  recordName = `_acme-challenge.${identifierValue}`;
}
// ... CNAME resolution for all providers ...
// Then, for the Azure DNS API call, make recordName relative to the configured zone:
// strip the trailing `.${zoneName}` from recordName before PUT .../dnsZones/{zone}/TXT/{recordName}

(Zone name can be derived from the hostedZoneId resource URI, whose last segment is the zone name.)

Environment

Observed on self-hosted v0.160.10; code unchanged on current main (verified via GitHub).