Caddy isn't sending the correct zone during ACME challenges?

1. The problem I’m having:

I’m trying to get caddy to create certificates for subdomains of a domain I own (khome.space). As recently as late last month I was able to have caddy create certificates for a number of subdomains (like code.khome.space) but as of a couple of days ago caddy can’t get a certificate for photos.khome.space.

2. Error messages and/or full log output:

It looks like something in the stack is sending space to Porkbun as the zone to check, which is incorrect.

I’m using what I believe is a common image for caddy and Porkbun, the included caddy-dns/porkbun module only reads the auth keys from the caddy file, there is no special logic for parsing zones.

I checked the image history and rolled back to the version available on July 21 (which is the last date I got a new cert from Let’s Encrypt for a subdomain, code.khome.space specifically), the behavior of using space was happened with that tag too.

DNS retrieve calls to Porkbun in the terminal return the correct information, from what I can tell, so this rules out Porkbun API problems.

curl -X POST https://api.porkbun.com/api/json/v3/dns/retrieve/khome.space -d '{"apikey":"KEY","secretapikey":"SECRET"}'

{
  "status": "SUCCESS",
  "cloudflare": "enabled",
  "records": [
    {
      "id": "543967828",
      "name": "*.khome.space",
      "type": "A",
      "content": "98.110.63.44",
      "ttl": "600",
      "prio": "0",
      "notes": ""
    },
    {
      "id": "543968568",
      "name": "khome.space",
      "type": "A",
      "content": "98.110.63.44",
      "ttl": "600",
      "prio": "0",
      "notes": ""
    },
    {
      "id": "575171121",
      "name": "libdns-set-address-new.khome.space",
      "type": "A",
      "content": "1.1.1.1",
      "ttl": "900",
      "prio": "0",
      "notes": ""
    },
    {
      "id": "575170213",
      "name": "libdns-set-address.khome.space",
      "type": "A",
      "content": "1.1.1.1",
      "ttl": "900",
      "prio": "0",
      "notes": ""
    },
    {
      "id": "543967827",
      "name": "khome.space",
      "type": "NS",
      "content": "curitiba.porkbun.com",
      "ttl": "86400",
      "prio": null,
      "notes": null
    },
    {
      "id": "543967826",
      "name": "khome.space",
      "type": "NS",
      "content": "fortaleza.porkbun.com",
      "ttl": "86400",
      "prio": null,
      "notes": null
    },
    {
      "id": "543967824",
      "name": "khome.space",
      "type": "NS",
      "content": "maceio.porkbun.com",
      "ttl": "86400",
      "prio": null,
      "notes": null
    },
    {
      "id": "543967825",
      "name": "khome.space",
      "type": "NS",
      "content": "salvador.porkbun.com",
      "ttl": "86400",
      "prio": null,
      "notes": null
    },
    {
      "id": "575171109",
      "name": "libdns.set-sub-new.khome.space",
      "type": "TXT",
      "content": "libdns_test_modify_sub_new_txt",
      "ttl": "900",
      "prio": "0",
      "notes": ""
    },
    {
      "id": "575170199",
      "name": "libdns.set-sub.khome.space",
      "type": "TXT",
      "content": "libdns_test_modify_sub_txt",
      "ttl": "900",
      "prio": "0",
      "notes": ""
    }
  ],
  "requestId": "01a00584-3589-78fa-984d-17a04cf5ef71"
}

Relevant logs from caddy.

2026-08-15 12:52:11.979 error {"level":"error","ts":1786812730.707065,"logger":"tls.obtain","msg":"will retry","error":"[photos.khome.space] Obtain: [photos.khome.space] solving challenges: presenting for challenge: adding temporary record for zone \"space.\": Invalid http response status, {\"status\":\"ERROR\",\"message\":\"Invalid domain.\",\"code\":\"INVALID_DOMAIN\",\"next_action\":{\"type\":\"fix_request\",\"hint\":\"The domain name is invalid. Check the formatting.\",\"retryable\":false},\"requestId\":\"01a00656-8b78-7116-b47b-4d729fc80fc4\"} for endpoint /dns/create/space (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/288924784/46504239194) (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":2,"retrying_in":120,"elapsed":62.464773454,"max_duration":2592000} 

2026-08-15 12:52:11.979 debug {"level":"debug","ts":1786812730.7070408,"logger":"events","msg":"event","name":"cert_failed","id":"0be473bf-cb67-4001-8bce-f9c9a1a2b639","origin":"tls","data":{"error":{},"identifier":"photos.khome.space","issuers":["acme-v02.api.letsencrypt.org-directory"],"renewal":false}} 

2026-08-15 12:52:11.979 error {"level":"error","ts":1786812730.7069876,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"photos.khome.space","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[photos.khome.space] solving challenges: presenting for challenge: adding temporary record for zone \"space.\": Invalid http response status, {\"status\":\"ERROR\",\"message\":\"Invalid domain.\",\"code\":\"INVALID_DOMAIN\",\"next_action\":{\"type\":\"fix_request\",\"hint\":\"The domain name is invalid. Check the formatting.\",\"retryable\":false},\"requestId\":\"01a00656-8b78-7116-b47b-4d729fc80fc4\"} for endpoint /dns/create/space (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/288924784/46504239194) (ca=https://acme-staging-v02.api.letsencrypt.org/directory)"} 

2026-08-15 12:52:11.979 error {"level":"error","ts":1786812730.6338983,"logger":"http.acme_client","msg":"cleaning up solver","identifier":"photos.khome.space","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.photos.khome.space\" (usually OK if presenting also failed)"} 

2026-08-15 12:52:11.978 debug {"level":"debug","ts":1786812729.943845,"logger":"tls.issuance.acme.dns_manager.dns_manager","msg":"creating DNS record","dns_name":"_acme-challenge.photos.khome.space","zone":"space.","record_name":"_acme-challenge.photos.khome","record_type":"TXT","record_data":"yxIZNwyNy7SP3WfPGoSKghz2lkdjGf_obQlgBRlh6s4","record_ttl":0} 

2026-08-15 12:52:11.978 info {"level":"info","ts":1786812729.9437807,"logger":"http.acme_client","msg":"trying to solve challenge","identifier":"photos.khome.space","challenge_type":"dns-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"} 

3. Caddy version:

v2.11.4 h1:XKxkMTgNSizEvKG6QHue6cAsFOteU2qA61w2tKkCWi0=

4. How I installed and ran Caddy:

I copied your compose file, included below.

services:
  caddy:
    image: srstone/caddy-porkbun:latest
    container_name: caddy
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ../certs:/data
      - ./config:/config
      - ./Caddyfile:/etc/caddy/Caddyfile
    environment:
      - CADDY_ADMIN=:2019
      - PORKBUN_API_KEY=KEY
      - PORKBUN_API_SECRET_KEY=SECRET

a. System environment:

Synology DS923+, DSM 7.3.2-86009 Update 4
Synology Container Manager
Arcane for container management

b. Command:

I manage this and other containers on my home lab with Arcane, though the behavior is the same if I start the container with docker compose up -d caddy.

c. Service/unit/compose file:

See above.

d. My complete Caddy config:

{
	debug
	admin :2019

	acme_dns porkbun {
		api_key {env.PORKBUN_API_KEY}
		api_secret_key {env.PORKBUN_API_SECRET_KEY}
	}
}

:2022 {
    route {
        caddy_admin_ui
        reverse_proxy localhost:2019 {
            header_up Host localhost:2019
        }
    }
}

(logging) {
	log {
		format console
	}
}

(debug-404) {
    header X-Debug-Karpodinis-Net "testing"
    respond "Not Found" 404
}

# Have caddy manage a certificate for my moquitto broker.
# This isn't actually a domain I'd want to have a service behind
# but I definitely want to secure communications between my zigbee
# devices and the broker.
mqtt.khome.space {
	reverse_proxy :9001 http://10.0.0.34:9001
}

zwave.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:8091
}

archive.khome.space {
	import logging
    reverse_proxy http://10.0.0.34:3050 {
        header_down Referrer-Policy "strict-origin-when-cross-origin"
    }
}

notes.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:5230
}

https://chore.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:2021
}

https://home.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:3060
}

https://cloud.khome.space {
	import logging
	rewrite https://cloud.khome.space/.well-known/openid-configuration https://cloud.khome.space/index.php/apps/openidconnect/config
	reverse_proxy http://10.0.0.34:5050
}

https://workout.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:3005
}

auth.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:9000
}

http://media.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:8096
}

budget.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:5006
}

https://fit.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:3004
}

code.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:3000
}

http://ha.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:8123
}

http://projects.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:3456
}

http://board.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:7575
}

alexander.khome.space {
	# Logging import, I do this everywhere
	import logging

	# Serve resume file
	handle /resume* {
		# Set the root directory for file service to my resume directory
		root * /volume1/homes/Alex/resumes

		# All requests satisfied by the above matcher should be rewritten to
		# serve resume.pdf in the root directory
		rewrite * /resume.pdf

		# gzip the file before sending
		encode gzip

		# Tell the browser not to cache the file, just in case I've updated it
		# This isn't great, but I don't have a reliable way to find the latest file
		# from a list of files and tell the browser to download it.
		# I should probably update this to map to the latest file to reduce the change
		# that a caller would fail to overwrite their local copy with the server copy?
		header Cache-Control "no-store"
	
		# Tell caddy to operate like a file server
		file_server
	}

    # Redirect to LinkedIn
    handle /linkediin {
        redir https://www.linkedin.com/in/alexander-karpodinis-31792547/ permanent
    }

    # Redirect to GitHub
    handle /github {
        redir https://www.github.com/akarpodinis permanent
    }

    # Block everything else
    handle {
        import debug-404
    }
}

money.khome.space {
	import logging
	reverse_proxy http://10.0.0.34:52675
}

docker.khome.space {
    import logging
    reverse_proxy http://10.0.0.34:3552
}

telemetry.khome.space {
    import logging
    reverse_proxy http://10.0.0.34:9000
}

logging.khome.space {
    import logging
    reverse_proxy http://10.0.0.34:52989
}

games.khome.space {
    import logging
    reverse_proxy http://10.0.0.34:5092
}

photos.khome.space {
    import logging
	reverse_proxy http://10.0.0.34:2283
}

5. Links to relevant resources:

Type here

Not an answer to your question, but just out of curiosity, why don’t you ask for a wildcard certificate once and reuse it for all your subdomains, instead of requesting a certificate for each subdomain separately?

You could just add this to your config:

*.khome.space {
    abort
}

I don’t remember why I didn’t have a wildcard matcher in here. I tried what you suggested and I’m seeing the same zone matching problem.

caddy  | {"level":"debug","ts":1786882371.834438,"logger":"http.acme_client","msg":"http request","method":"POST","url":"https://acme-v02.api.letsencrypt.org/acme/authz/2992294076/760742322801","headers":{"Content-Type":["application/jose+json"],"User-Agent":["Caddy/2.11.4 CertMagic acmez (linux; amd64)"]},"response_headers":{"Boulder-Requester":["2992294076"],"Cache-Control":["public, max-age=0, no-cache"],"Content-Length":["397"],"Content-Type":["application/json"],"Date":["Sun, 16 Aug 2026 12:12:51 GMT"],"Link":["<https://acme-v02.api.letsencrypt.org/directory>;rel=\"index\""],"Replay-Nonce":["huEC4XJSDNb8OSGZh_XDPAc133JH-i5gjdT1ysGeaV6oYVc8eVY"],"Server":["nginx"],"Strict-Transport-Security":["max-age=604800"],"X-Frame-Options":["DENY"]},"status_code":200}
caddy  | {"level":"error","ts":1786882371.8345823,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"*.khome.space","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[*.khome.space] solving challenges: presenting for challenge: adding temporary record for zone \"space.\": Invalid http response status, {\"status\":\"ERROR\",\"message\":\"Invalid domain.\",\"code\":\"INVALID_DOMAIN\",\"next_action\":{\"type\":\"fix_request\",\"hint\":\"The domain name is invalid. Check the formatting.\",\"retryable\":false},\"requestId\":\"01a00a7d-2f09-7b87-90e1-faab1fc51c2f\"} for endpoint /dns/create/space (order=https://acme-v02.api.letsencrypt.org/acme/order/2992294076/545671497681) (ca=https://acme-v02.api.letsencrypt.org/directory)"}
caddy  | {"level":"debug","ts":1786882371.8346236,"logger":"events","msg":"event","name":"cert_failed","id":"cd724957-bec2-4518-b52f-54dfc791dcb1","origin":"tls","data":{"error":{},"identifier":"*.khome.space","issuers":["acme-v02.api.letsencrypt.org-directory"],"renewal":false}}
caddy  | {"level":"error","ts":1786882371.834662,"logger":"tls.obtain","msg":"will retry","error":"[*.khome.space] Obtain: [*.khome.space] solving challenges: presenting for challenge: adding temporary record for zone \"space.\": Invalid http response status, {\"status\":\"ERROR\",\"message\":\"Invalid domain.\",\"code\":\"INVALID_DOMAIN\",\"next_action\":{\"type\":\"fix_request\",\"hint\":\"The domain name is invalid. Check the formatting.\",\"retryable\":false},\"requestId\":\"01a00a7d-2f09-7b87-90e1-faab1fc51c2f\"} for endpoint /dns/create/space (order=https://acme-v02.api.letsencrypt.org/acme/order/2992294076/545671497681) (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":1.282561752,"max_duration":2592000}

So, this would probably have worked a week ago when I wasn’t having this problem, but it would have appeared in about two months when Caddy would re-request the certificate.

@timelordx would that be useful to add a wildcard to a bare domain with existing SSL certificate?

It depends.

If you have a very public site and don’t care that everyone knows about its subdomains, then no, it probably wouldn’t make much difference.

If you have sites or subdomains that you don’t want to advertise, using a wildcard certificate can help avoid exposing their names.

When a public CA issues a certificate, information about it is published to Certificate Transparency logs. If I know your domain, I can write a one-liner Bash script that pulls a list of certificates and subdomains from those logs.

If all I get back is *.yourdomain.com instead of certificates containing individual hostnames, there are fewer obvious targets for me to poke around. You can reduce the exposure even further by using a wildcard DNS entry for your sites.

It’s never 100% bulletproof, but it reduces the chances of your sites being discovered.

You can see a use-case example here, where Caddy pretty much drops you if you don’t talk to it using the correct name (SNI/Host):

I mean, there are still ways to make Caddy reveal itself even with such a setup, but there’s less chance of it being discovered by script kiddies :slight_smile:

Thank you!

I probably should have been more specific. I have a bare domain with an A & AAAA which I tried: *.fqdn fqdn {

via Caddyfile thinking it was the way to get both certs. The DNS-01 confirmed the wildcard immediately but then the bare never would complete over the next 1-2 days. I ditched the wildcard from the config and the bare domain was confirmed in minutes.

I was thinking maybe the example you had posted was the way to get both certs buy adding that to the Caddyfile. I’m not particularly looking to hide sub-domains, but was thinking about being already set if in the future I did add a sub-domain.

No. A wildcard certificate for *.example.com only covers one level of subdomains, for example, foo.example.com or bar.example.com.

It does not cover the bare domain example.com, nor does it cover deeper subdomains like foo.bar.example.com.

I do not want to detour this post, so if you’d like us to continue, I’d say we create another post. But I want to mention that I’m using this deployment at home:

and example.com, *.example.com { works without a problem.

I’m going to re-post this since it was hijacked by another topic!