Issue Details
I wasn't able to access one of my subdomains after updating to 2.11.4.
Debugged with Claude so not sure how accurate the issue is but debugged for a longer than I'd care to admit before finally going back to 2.11.3.
Perhaps some literal embodied quote characters causing some issues. e.g. "OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw" instead of OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw
Hope I don't get crucified for opening up this issue but just in case anyone else faces the same issue hopefully this helps. Below is a dump of the write up Claude did regarding the issue... I went through it's basically the same as everything I'd done with it during my attempt to debug. Hope it helps with getting a patch for the issue.
Summary
When Caddy creates the _acme-challenge TXT record for a DNS-01 challenge via this module, the record's content field on Cloudflare ends up with literal double-quote characters wrapped around the actual token value, e.g.:
"OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw"
instead of the expected:
OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw
Because Caddy's own propagation check (certmagic.solverWrapper) compares the literal expected token against what it resolves over DNS, this mismatch means the check can never succeed, regardless of resolver used or how long propagation_timeout is set. Every obtain/renew attempt fails after the timeout with:
solving challenges: waiting for solver certmagic.solverWrapper to be ready:
timed out waiting for record to fully propagate; verify DNS provider
configuration is correct
Environment
- Caddy version:
2.11.4 (image: ghcr.io/caddybuilds/caddy-cloudflare:2.11.4) - Module version:
dns.providers.cloudflare v0.2.4 (per caddy list-modules --versions) - DNS provider: Cloudflare (single API token,
Zone:DNS:Edit + Zone:Read scoped) - Caddyfile relevant snippet:
caddyfile
(cf_dns) {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
resolvers 1.1.1.1 1.0.0.1
}
}
example.sub.yourdomain.com {
import cf_dns
reverse_proxy 127.0.0.1:1234
}
Steps to reproduce
- Configure a site with the Cloudflare DNS-01 challenge as above.
- Trigger a certificate obtain (e.g. fresh container start for a domain with no existing cert).
- While the attempt is in progress, query Cloudflare's API directly for the created record:
curl -s "https://api.cloudflare.com/client/v4/zones/<zone_id>/dns_records?type=TXT&name=_acme-challenge.example.sub.yourdomain.com" \
-H "Authorization: Bearer <token>"
- Observe the
content field includes literal \" characters wrapping the value.
Evidence
For comparison, here are three TXT records that existed simultaneously at the same name (_acme-challenge.<subdomain>.yourdomain.com) during debugging:
Source | content field (raw, as returned by Cloudflare API) | ttl
-- | -- | --
Manual curl -X POST (plain content, no quotes in payload) | manual-test-value-12345 | 120
Caddy ACME attempt
#1 | "OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw" | 1
Caddy ACME attempt
#2 | "F3v_kA6KMzm88n5gooov9aH26bzKwhVu7q7SepNQQY0" | 1
The manually-created record (content submitted with no surrounding quotes) was stored and returned by Cloudflare exactly as submitted — no quotes added. Both Caddy-created records, from the same token and same zone, came back with literal quote characters baked into the content.
This was not a propagation/caching/resolver issue — ruled out by:
- Setting explicit
resolvers 1.1.1.1 1.0.0.1 in the tls block (no change in behavior) - Querying the zone's authoritative nameserver directly (
jake.ns.cloudflare.com) during an active attempt — record never matched because of the quote mismatch, not because it was absent - Confirming via the Cloudflare API directly that the record existed in the zone the entire time, just with malformed content
Expected behavior
The TXT record content written to Cloudflare should be the raw challenge token, with no additional quote characters, matching what a manual API call with the same payload produces.
Actual behavior
The module (or an underlying dependency, possibly related to the recent libdns 1.0 API migration) appears to be double-wrapping the content value in quotes before submitting it to Cloudflare's API, resulting in a permanently broken DNS-01 challenge.
Regression window / workaround
This appears to be a regression introduced between the module version bundled with Caddy 2.11.3 and 2.11.4. Pinning to the 2.11.3-based image resolves the issue completely with an otherwise identical Caddyfile and Cloudflare token. I have not yet identified the exact caddy-dns/cloudflare (or libdns/cloudflare) version bundled with the 2.11.3 build for direct comparison — happy to pull that if useful for triage.
Additional context
Cloudflare's own API/dashboard does have a known general quirk around auto-wrapping TXT content in quotes in some circumstances (acknowledged in their own UI warning, and reported against several other tools: terraform-provider-cloudflare#4650, certbot#10048). That said, the fact that a manually-submitted unquoted payload was stored unquoted, while the module's own writes were quoted, suggests the quoting is happening on the request-construction side here rather than purely a Cloudflare-side inconsistency — but I'm not certain of the exact mechanism without digging into the module's source.
Summary
When Caddy creates the _acme-challenge TXT record for a DNS-01 challenge via this module, the record's content field on Cloudflare ends up with literal double-quote characters wrapped around the actual token value, e.g.:
"OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw"
instead of the expected:
OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw
Because Caddy's own propagation check (certmagic.solverWrapper) compares the literal expected token against what it resolves over DNS, this mismatch means the check can never succeed, regardless of resolver used or how long propagation_timeout is set. Every obtain/renew attempt fails after the timeout with:
solving challenges: waiting for solver certmagic.solverWrapper to be ready:
timed out waiting for record to fully propagate; verify DNS provider
configuration is correct
Environment
Caddy version: 2.11.4 (image: ghcr.io/caddybuilds/caddy-cloudflare:2.11.4)
Module version: dns.providers.cloudflare v0.2.4 (per caddy list-modules --versions)
DNS provider: Cloudflare (single API token, Zone:DNS:Edit + Zone:Read scoped)
Caddyfile relevant snippet:
caddyfile (cf_dns) {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
resolvers 1.1.1.1 1.0.0.1
}
}
example.sub.yourdomain.com {
import cf_dns
reverse_proxy 127.0.0.1:1234
}
Steps to reproduce
Configure a site with the Cloudflare DNS-01 challenge as above.
Trigger a certificate obtain (e.g. fresh container start for a domain with no existing cert).
While the attempt is in progress, query Cloudflare's API directly for the created record:
curl -s "https://api.cloudflare.com/client/v4/zones/<zone_id>/dns_records?type=TXT&name=_acme-challenge.example.sub.yourdomain.com"
-H "Authorization: Bearer "
Observe the content field includes literal " characters wrapping the value.
Evidence
For comparison, here are three TXT records that existed simultaneously at the same name (_acme-challenge..yourdomain.com) during debugging:
Sourcecontent field (raw, as returned by Cloudflare API)ttlManual curl -X POST (plain content, no quotes in payload)manual-test-value-12345120Caddy ACME attempt #1"OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw"1Caddy ACME attempt #2"F3v_kA6KMzm88n5gooov9aH26bzKwhVu7q7SepNQQY0"1
The manually-created record (content submitted with no surrounding quotes) was stored and returned by Cloudflare exactly as submitted — no quotes added. Both Caddy-created records, from the same token and same zone, came back with literal quote characters baked into the content.
This was not a propagation/caching/resolver issue — ruled out by:
Setting explicit resolvers 1.1.1.1 1.0.0.1 in the tls block (no change in behavior)
Querying the zone's authoritative nameserver directly (jake.ns.cloudflare.com) during an active attempt — record never matched because of the quote mismatch, not because it was absent
Confirming via the Cloudflare API directly that the record existed in the zone the entire time, just with malformed content
Expected behavior
The TXT record content written to Cloudflare should be the raw challenge token, with no additional quote characters, matching what a manual API call with the same payload produces.
Actual behavior
The module (or an underlying dependency, possibly related to the recent libdns 1.0 API migration) appears to be double-wrapping the content value in quotes before submitting it to Cloudflare's API, resulting in a permanently broken DNS-01 challenge.
Regression window / workaround
This appears to be a regression introduced between the module version bundled with Caddy 2.11.3 and 2.11.4. Pinning to the 2.11.3-based image resolves the issue completely with an otherwise identical Caddyfile and Cloudflare token. I have not yet identified the exact caddy-dns/cloudflare (or libdns/cloudflare) version bundled with the 2.11.3 build for direct comparison — happy to pull that if useful for triage.
Additional context
Cloudflare's own API/dashboard does have a known general quirk around auto-wrapping TXT content in quotes in some circumstances (acknowledged in their own UI warning, and reported against several other tools: terraform-provider-cloudflare#4650, certbot#10048). That said, the fact that a manually-submitted unquoted payload was stored unquoted, while the module's own writes were quoted, suggests the quoting is happening on the request-construction side here rather than purely a Cloudflare-side inconsistency — but I'm not certain of the exact mechanism without digging into the module's source.
Assistance Disclosure
AI used
If AI was used, describe the extent to which it was used.
used Claude to debug the issue and write up parts of the issue
Issue Details
I wasn't able to access one of my subdomains after updating to 2.11.4.
Debugged with Claude so not sure how accurate the issue is but debugged for a longer than I'd care to admit before finally going back to 2.11.3.
Perhaps some literal embodied quote characters causing some issues. e.g. "OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw" instead of OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw
Hope I don't get crucified for opening up this issue but just in case anyone else faces the same issue hopefully this helps. Below is a dump of the write up Claude did regarding the issue... I went through it's basically the same as everything I'd done with it during my attempt to debug. Hope it helps with getting a patch for the issue.
Summary
When Caddy creates the
_acme-challengeTXT record for a DNS-01 challenge via this module, the record'scontentfield on Cloudflare ends up with literal double-quote characters wrapped around the actual token value, e.g.:instead of the expected:
Because Caddy's own propagation check (
certmagic.solverWrapper) compares the literal expected token against what it resolves over DNS, this mismatch means the check can never succeed, regardless of resolver used or how longpropagation_timeoutis set. Every obtain/renew attempt fails after the timeout with:Environment
2.11.4(image:ghcr.io/caddybuilds/caddy-cloudflare:2.11.4)dns.providers.cloudflare v0.2.4(percaddy list-modules --versions)Zone:DNS:Edit+Zone:Readscoped)Steps to reproduce
contentfield includes literal\"characters wrapping the value.Evidence
For comparison, here are three TXT records that existed simultaneously at the same name (
_acme-challenge.<subdomain>.yourdomain.com) during debugging:The manually-created record (content submitted with no surrounding quotes) was stored and returned by Cloudflare exactly as submitted — no quotes added. Both Caddy-created records, from the same token and same zone, came back with literal quote characters baked into the content.
This was not a propagation/caching/resolver issue — ruled out by:
resolvers 1.1.1.1 1.0.0.1in thetlsblock (no change in behavior)jake.ns.cloudflare.com) during an active attempt — record never matched because of the quote mismatch, not because it was absentExpected behavior
The TXT record content written to Cloudflare should be the raw challenge token, with no additional quote characters, matching what a manual API call with the same payload produces.
Actual behavior
The module (or an underlying dependency, possibly related to the recent
libdns1.0 API migration) appears to be double-wrapping the content value in quotes before submitting it to Cloudflare's API, resulting in a permanently broken DNS-01 challenge.Regression window / workaround
This appears to be a regression introduced between the module version bundled with Caddy
2.11.3and2.11.4. Pinning to the2.11.3-based image resolves the issue completely with an otherwise identical Caddyfile and Cloudflare token. I have not yet identified the exactcaddy-dns/cloudflare(orlibdns/cloudflare) version bundled with the2.11.3build for direct comparison — happy to pull that if useful for triage.Additional context
Cloudflare's own API/dashboard does have a known general quirk around auto-wrapping TXT content in quotes in some circumstances (acknowledged in their own UI warning, and reported against several other tools: terraform-provider-cloudflare#4650, certbot#10048). That said, the fact that a manually-submitted unquoted payload was stored unquoted, while the module's own writes were quoted, suggests the quoting is happening on the request-construction side here rather than purely a Cloudflare-side inconsistency — but I'm not certain of the exact mechanism without digging into the module's source.
SummaryWhen Caddy creates the _acme-challenge TXT record for a DNS-01 challenge via this module, the record's content field on Cloudflare ends up with literal double-quote characters wrapped around the actual token value, e.g.:
"OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw"
instead of the expected:
OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw
Because Caddy's own propagation check (certmagic.solverWrapper) compares the literal expected token against what it resolves over DNS, this mismatch means the check can never succeed, regardless of resolver used or how long propagation_timeout is set. Every obtain/renew attempt fails after the timeout with:
solving challenges: waiting for solver certmagic.solverWrapper to be ready:
timed out waiting for record to fully propagate; verify DNS provider
configuration is correct
Environment
Caddy version: 2.11.4 (image: ghcr.io/caddybuilds/caddy-cloudflare:2.11.4)
Module version: dns.providers.cloudflare v0.2.4 (per caddy list-modules --versions)
DNS provider: Cloudflare (single API token, Zone:DNS:Edit + Zone:Read scoped)
Caddyfile relevant snippet:
caddyfile (cf_dns) {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
resolvers 1.1.1.1 1.0.0.1
}
}
example.sub.yourdomain.com {
import cf_dns
reverse_proxy 127.0.0.1:1234
}
Steps to reproduce
Configure a site with the Cloudflare DNS-01 challenge as above.
Trigger a certificate obtain (e.g. fresh container start for a domain with no existing cert).
While the attempt is in progress, query Cloudflare's API directly for the created record:
curl -s "https://api.cloudflare.com/client/v4/zones/<zone_id>/dns_records?type=TXT&name=_acme-challenge.example.sub.yourdomain.com"
-H "Authorization: Bearer "
Observe the content field includes literal " characters wrapping the value.
Evidence
For comparison, here are three TXT records that existed simultaneously at the same name (_acme-challenge..yourdomain.com) during debugging:
Sourcecontent field (raw, as returned by Cloudflare API)ttlManual curl -X POST (plain content, no quotes in payload)manual-test-value-12345120Caddy ACME attempt #1"OQJskOaeWRBc82svCMEYT99uN0gYhadxU5vscYMBibw"1Caddy ACME attempt #2"F3v_kA6KMzm88n5gooov9aH26bzKwhVu7q7SepNQQY0"1
The manually-created record (content submitted with no surrounding quotes) was stored and returned by Cloudflare exactly as submitted — no quotes added. Both Caddy-created records, from the same token and same zone, came back with literal quote characters baked into the content.
This was not a propagation/caching/resolver issue — ruled out by:
Setting explicit resolvers 1.1.1.1 1.0.0.1 in the tls block (no change in behavior)
Querying the zone's authoritative nameserver directly (jake.ns.cloudflare.com) during an active attempt — record never matched because of the quote mismatch, not because it was absent
Confirming via the Cloudflare API directly that the record existed in the zone the entire time, just with malformed content
Expected behavior
The TXT record content written to Cloudflare should be the raw challenge token, with no additional quote characters, matching what a manual API call with the same payload produces.
Actual behavior
The module (or an underlying dependency, possibly related to the recent libdns 1.0 API migration) appears to be double-wrapping the content value in quotes before submitting it to Cloudflare's API, resulting in a permanently broken DNS-01 challenge.
Regression window / workaround
This appears to be a regression introduced between the module version bundled with Caddy 2.11.3 and 2.11.4. Pinning to the 2.11.3-based image resolves the issue completely with an otherwise identical Caddyfile and Cloudflare token. I have not yet identified the exact caddy-dns/cloudflare (or libdns/cloudflare) version bundled with the 2.11.3 build for direct comparison — happy to pull that if useful for triage.
Additional context
Cloudflare's own API/dashboard does have a known general quirk around auto-wrapping TXT content in quotes in some circumstances (acknowledged in their own UI warning, and reported against several other tools: terraform-provider-cloudflare#4650, certbot#10048). That said, the fact that a manually-submitted unquoted payload was stored unquoted, while the module's own writes were quoted, suggests the quoting is happening on the request-construction side here rather than purely a Cloudflare-side inconsistency — but I'm not certain of the exact mechanism without digging into the module's source.
Assistance Disclosure
AI used
If AI was used, describe the extent to which it was used.
used Claude to debug the issue and write up parts of the issue