Caddy CloudFlare DDNS fails; DNS verification works; error: expected 1 zone, got 0

1. The problem I’m having:

Caddy not creating/updating Dynamic DNS CloudFlare records; Wildcard domain LetsEncrypt DNS verification and issuance DOES work (Same API key)

Caddy has no issues creating the necessary TXT records for LetsEncrypt DNS validation but fails to create and/or update the necessary A records for domains / subdomains

Permissions on CloudFlare Token used:

I have not enabled the DNS record proxy feature on CloudFlare

What I have tried

On CloudFlare, manually create A record omv.ext.consultoria-as.com pointing to 192.168.10.101 so that Caddy can update it to the correct external IP, thinking that perhaps this is an error specific to creating the DNS record and editing an existing one might work; same error

2. Error messages and/or full log output:

2025-03-04T18:24:25	Informational	caddy	 "info","ts":"2025-03-04T18:24:25Z","logger":"dynamic_dns","msg":"finished updating DNS","current_ips":["201.142.166.224"]}
**2025-03-04T18:24:25	Error	caddy	 "error","ts":"2025-03-04T18:24:25Z","logger":"dynamic_dns","msg":"failed setting DNS record(s) with new IP address(es)","zone":"ext.consultoria-as.com","error":"expected 1 zone, got 0 for ext.consultoria-as.com"}**
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"dynamic_dns","msg":"updating DNS record","zone":"ext.consultoria-as.com","type":"A","name":"grist","value":"201.142.166.224","ttl":0}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"dynamic_dns","msg":"updating DNS record","zone":"ext.consultoria-as.com","type":"A","name":"OMV","value":"201.142.166.224","ttl":0}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"dynamic_dns.ip_sources.simple_http","msg":"lookup","type":"IPv4","endpoint":"https://icanhazip.com","ip":"201.142.166.224"}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"dynamic_dns","msg":"looked up current IPs from DNS","lastIPs":null}
2025-03-04T18:24:24	Error	caddy	 "error","ts":"2025-03-04T18:24:24Z","logger":"dynamic_dns","msg":"unable to lookup current IPs from DNS records","error":"expected 1 zone, got 0 for ext.consultoria-as.com"}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"tls","msg":"finished cleaning storage units"}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/var/db/caddy/data/caddy","instance":"2472f685-358d-401d-b08f-a2b9cf5002cf","try_again":"2025-03-05T18:24:24Z","try_again_in":86399.999998845}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","msg":"serving initial configuration"}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","msg":"autosaved config (load with --resume flag)","file":"/var/db/caddy/config/caddy/autosave.json"}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"dynamic_dns","msg":"beginning IP address check"}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"events","msg":"event","name":"cached_managed_cert","id":"a4e1431b-c081-43ef-8dee-9c7a93e739fe","origin":"tls","data":{"sans":["*.ext.consultoria-as.com"]}}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"tls.cache","msg":"added certificate to cache","subjects":["*.ext.consultoria-as.com"],"expiration":"2025-06-02T01:42:38Z","managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"69663cf76afa6d5048b4f0a2287d2271396b3716608110391eab48d61b749f54","cache_size":1,"cache_capacity":10000}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"http","msg":"enabling automatic TLS certificate management","domains":["*.ext.consultoria-as.com"]}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
2025-03-04T18:24:24	Warning	caddy	 "warn","ts":"2025-03-04T18:24:24Z","logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
2025-03-04T18:24:24	Warning	caddy	 "warn","ts":"2025-03-04T18:24:24Z","logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":false}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x8709c8280"}
2025-03-04T18:24:24	Debug	caddy	 "debug","ts":"2025-03-04T18:24:24Z","logger":"http.auto_https","msg":"adjusted config","tls":{"automation":{"policies":[{"subjects":["*.ext.consultoria-as.com"]},{}]}},"http":{"http_port":80,"https_port":443,"grace_period":10000000000,"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"group":"group4","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"rewrite","strip_path_prefix":"/omv"}]},{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"192.168.10.5:80"}]}]}]}],"match":[{"path":["/omv/*"]}]}]}],"match":[{"host":["OMV.ext.consultoria-as.com"]}]},{"group":"group4","handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"192.168.10.12:8484"}]}]}]}]}]}],"match":[{"host":["grist.ext.consultoria-as.com"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{},"protocols":["h1","h2","h3"]}}}}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
2025-03-04T18:24:24	Informational	caddy	 "info","ts":"2025-03-04T18:24:24Z","logger":"admin","msg":"admin endpoint started","address":"unix//var/run/caddy/caddy.sock|0220","enforce_origin":false,"origins":["//127.0.0.1","//::1",""]}
2025-03-04T18:24:23	Informational	caddy	 "info","ts":"2025-03-04T18:24:23Z","logger":"admin.api","msg":"shutdown complete","exit_code":0}
2025-03-04T18:24:23	Informational	caddy	 "info","ts":"2025-03-04T18:24:23Z","logger":"admin","msg":"stopped previous server","address":"unix//var/run/caddy/caddy.sock|0220"}
2025-03-04T18:24:23	Informational	caddy	 "info","ts":"2025-03-04T18:24:23Z","logger":"http","msg":"servers shutting down; grace period initiated","duration":10}
2025-03-04T18:24:23	Warning	caddy	 "warn","ts":"2025-03-04T18:24:23Z","logger":"admin.api","msg":"exiting; byeee!! 👋"}
2025-03-04T18:24:23	Informational	caddy	 "info","ts":"2025-03-04T18:24:23Z","logger":"admin.api","msg":"received request","method":"POST","host":"127.0.0.1","uri":"/stop","remote_ip":"","remote_port":"","headers":{"Accept-Encoding":["gzip"],"Content-Length":["0"],"User-Agent":["Go-http-client/1.1"]}}

3. Caddy version:

4. How I installed and ran Caddy:

a. System environment:

Caddy 1.8.2

OPNsense 25.1.2-amd64
FreeBSD 14.2-RELEASE-p2
OpenSSL 3.0.16

b. Command:

N/A

c. Service/unit/compose file:

N/A

d. My complete Caddy config:

# DO NOT EDIT THIS FILE -- OPNsense auto-generated file


# caddy_user=root

# Global Options
{
	log {
		output net unixgram//var/run/caddy/log.sock {
		}
		format json {
			time_format rfc3339
		}
		level DEBUG
	}

	http_port 80
	https_port 443

	servers {
		protocols h1 h2 h3
	}

	dynamic_dns {
		provider cloudflare CF_Token_Redacted
		domains {
			ext.consultoria-as.com omv
			ext.consultoria-as.com grist
		}
		versions ipv4
	}

	email ialcarazsalazar@consultoria-as.com
	grace_period 10s
	import /usr/local/etc/caddy/caddy.d/*.global
}

# Reverse Proxy Configuration


# Reverse Proxy Domain: "2e538277-9711-4f3d-a98e-eb41b1936d04"
*.ext.consultoria-as.com {
	tls {
		issuer acme {
			dns cloudflare CF_Token_Redacted
			resolvers 1.1.1.1
		}
	}

	@7caef048-af97-4008-be37-1201cca04a9f {
		host omv.ext.consultoria-as.com
	}
	handle @7caef048-af97-4008-be37-1201cca04a9f {
		handle {
			reverse_proxy 192.168.10.5:80 {
				transport http {
				}
			}
		}
	}
	@26160705-9555-4d50-aa50-afa00d5e74d2 {
		host grist.ext.consultoria-as.com
	}
	handle @26160705-9555-4d50-aa50-afa00d5e74d2 {
		handle {
			reverse_proxy 192.168.10.12:8484 {
			}
		}
	}
}

import /usr/local/etc/caddy/caddy.d/*.conf

5. Links to relevant resources:

That’s a very old version of Caddy you’re using. Any reason you have decided to not update it?

I personally don’t use Cloudflare, but shouldn’t there be both an API token and a zone token? So it would be like this:

*.ext.consultoria-as.com {
	tls {
		issuer acme {
			dns cloudflare {
				api_token CF_Token_Redacted
				zone_token your-zone-token
			resolvers 1.1.1.1
		}
	}

Thanks for the reply and for pointing out the old version, I incorrectly used the OPNsense plugin version instead of the Caddy version, just reviewed and confirmed that I am running Caddy 2.9.1

I have now tried to edit the post but apparently it is not allowed anymore, hoping a moderator can do it for me :slight_smile:

Regarding the suggestion of adding zone_token; per the guide, that is needed if I was using the 2 token approach, but in my case I have a single token with all the permissions needed for CloudFlare to update records

What is strange is that Caddy can update DNS TXT records for LetsEncrypt validation, but fails with DDNS

When I look up the DNS record for your domain, it’s a private IP. DynamicDNS updates public IPs. Is your configuration accurate to what you want?

ext.consultoria-as.com is indeed currently set to a private IP, this was done as an earlier test to see if Caddy DDNS could update the record if already created

omv.ext.consultoria-as.com and grist.ext.consultoria-as.com are the records that I actually wanted Caddy to update for me, they are currently being manually updated as needed since I could not get Caddy DDNS to work

I have modified the settings a few times to confirm I am not missing anything, I keep getting errors like:

"logger":"dynamic_dns","msg":"failed setting DNS record(s) with new IP address(es)","zone":"ext.consultoria-as.com","error":"expected 1 zone, got 0 for ext.consultoria-as.com"}

That’s what I mean. DynamicDNS cannot update something if it is in the private IP range. If you use either of those other domains instead, it should work, as the IP assigned to the A record is 187.250.129.165 which is public. Assuming everything is configured correctly, anyway.

I was not aware of that, maybe I missed it in the documentation?

In any case, I have tested per your suggestion:

Test 1: Have Caddy DDNS create the A record for me

In Caddy, I added test01.ext.consultoria-as.com as a subdomain, enabled DDNS, did not created a matching record in CloudFlare DNS, so that Caddy DDNS creates it for me

I get this right after:

2025-03-07T19:17:24	Informational	caddy	"info","ts":"2025-03-07T19:17:24Z","logger":"dynamic_dns","msg":"finished updating DNS","current_ips":["187.250.129.165"]}	
2025-03-07T19:17:24	Error	caddy	"error","ts":"2025-03-07T19:17:24Z","logger":"dynamic_dns","msg":"failed setting DNS record(s) with new IP address(es)","zone":"ext.consultoria-as.com","error":"expected 1 zone, got 0 for ext.consultoria-as.com"}	
2025-03-07T19:17:23	Informational	caddy	"info","ts":"2025-03-07T19:17:23Z","logger":"dynamic_dns","msg":"updating DNS record","zone":"ext.consultoria-as.com","type":"A","name":"test01","value":"187.250.129.165","ttl":0}	
2025-03-07T19:17:23	Debug	caddy	"debug","ts":"2025-03-07T19:17:23Z","logger":"dynamic_dns.ip_sources.simple_http","msg":"lookup","type":"IPv4","endpoint":"https://icanhazip.com","ip":"187.250.129.165"}	
2025-03-07T19:17:23	Informational	caddy	"info","ts":"2025-03-07T19:17:23Z","logger":"admin","msg":"stopped previous server","address":"unix//var/run/caddy/caddy.sock|0220"}	
2025-03-07T19:17:23	Informational	caddy	"info","ts":"2025-03-07T19:17:23Z","logger":"admin.api","msg":"load complete"}	
2025-03-07T19:17:23	Informational	caddy	"info","ts":"2025-03-07T19:17:23Z","msg":"autosaved config (load with --resume flag)","file":"/var/db/caddy/config/caddy/autosave.json"}	
2025-03-07T19:17:23	Debug	caddy	"debug","ts":"2025-03-07T19:17:23Z","logger":"dynamic_dns","msg":"beginning IP address check"}

Test 2: Create the A record in advance using a non-private IP

Go to CloudFlare DNS, create an A record for test01.ext.consultoria-as.com set it to a value of 187.250.100.200 a random WAN IP address, restarted Caddy to have it update the IP, same error:

2025-03-07T20:18:21	Informational	caddy	"info","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns","msg":"finished updating DNS","current_ips":["187.250.129.165"]}	
2025-03-07T20:18:21	Error	caddy	"error","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns","msg":"failed setting DNS record(s) with new IP address(es)","zone":"ext.consultoria-as.com","error":"expected 1 zone, got 0 for ext.consultoria-as.com"}	
2025-03-07T20:18:21	Informational	caddy	"info","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns","msg":"updating DNS record","zone":"ext.consultoria-as.com","type":"A","name":"test01","value":"187.250.129.165","ttl":0}	
2025-03-07T20:18:21	Informational	caddy	"info","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns","msg":"updating DNS record","zone":"ext.consultoria-as.com","type":"A","name":"omv","value":"187.250.129.165","ttl":0}	
2025-03-07T20:18:21	Debug	caddy	"debug","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns.ip_sources.simple_http","msg":"lookup","type":"IPv4","endpoint":"https://icanhazip.com","ip":"187.250.129.165"}	
2025-03-07T20:18:21	Debug	caddy	"debug","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns","msg":"looked up current IPs from DNS","lastIPs":null}	
2025-03-07T20:18:21	Error	caddy	"error","ts":"2025-03-07T20:18:21Z","logger":"dynamic_dns","msg":"unable to lookup current IPs from DNS records","error":"expected 1 zone, got 0 for ext.consultoria-as.com"}	
2025-03-07T20:18:21	Informational	caddy	"info","ts":"2025-03-07T20:18:21Z","msg":"serving initial configuration"}	
2025-03-07T20:18:21	Informational	caddy	"info","ts":"2025-03-07T20:18:21Z","msg":"autosaved config (load with --resume flag)","file":"/var/db/caddy/config/caddy/autosave.json"}	
2025-03-07T20:18:21	Debug	caddy	"debug","ts":"2025-03-07T20:18:21Z","logger":"events","msg":"event","name":"cached_managed_cert","id":"36069bd5-37c5-40ba-9fe0-a5ec09c1d8fe","origin":"tls","data":{"sans":["*.ext.consultoria-as.com"]}}	
2025-03-07T20:18:21	Debug	caddy	"debug","ts":"2025-03-07T20:18:21Z","logger":"tls.cache","msg":"added certificate to cache","subjects":["*.ext.consultoria-as.com"],"expiration":"2025-06-02T01:42:38Z","managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"69663cf76afa6d5048b4f0a2287d2271396b3716608110391eab48d61b749f54","cache_size":1,"cache_capacity":10000}	
2025-03-07T20:18:20	Informational	caddy	"info","ts":"2025-03-07T20:18:20Z","logger":"tls","msg":"finished cleaning storage units"}	
2025-03-07T20:18:20	Informational	caddy	"info","ts":"2025-03-07T20:18:20Z","logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/var/db/caddy/data/caddy","instance":"2472f685-358d-401d-b08f-a2b9cf5002cf","try_again":"2025-03-08T20:18:20Z","try_again_in":86399.999998964}	
2025-03-07T20:18:20	Debug	caddy	"debug","ts":"2025-03-07T20:18:20Z","logger":"dynamic_dns","msg":"beginning IP address check"}

I wasn’t either until I used a search engine to verify. The documentation here only refers to public IP, though.

The only thing I can think of by this point are three things:

  1. Verify zone ID and any setting in any configuration that needs it.
  2. Ensure the domain of ext.consultoria-as.com is not a private IP. I’m not entirely sure if this is necessary or not.
  3. You could try to get a specific certificate for one of those sub-subdomains and see if that changes anything.

Hoping someone can chime in.

Not seeing other people reporting this particular problem here, starting to think this could be an issue with the underlying package used to interact with Cloudflare or perhaps with OPNsense implementation?

Will look into reporting it on Github