DNS challenge with hetzner plugin does not work

1. The problem I’m having:

I am trying to set up caddy for a domain at hetzner. I want to use the DNS challenge but it does not seem to work.

2. Error messages and/or full log output:

This is the log I get when trying to start the container with docker compose up:

[+] Running 1/1
 ✔ Container caddy_hetzner  Created                                                                                                                                                                                               0.0s
Attaching to caddy_hetzner
caddy_hetzner  | {"level":"info","ts":1771107262.685675,"msg":"maxprocs: Leaving GOMAXPROCS=2: CPU quota undefined"}
caddy_hetzner  | {"level":"info","ts":1771107262.6867218,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":3691656806,"previous":9223372036854775807}
caddy_hetzner  | {"level":"info","ts":1771107262.6868136,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
caddy_hetzner  | {"level":"info","ts":1771107262.692738,"msg":"adapted config to JSON","adapter":"caddyfile"}
caddy_hetzner  | {"level":"warn","ts":1771107262.6937575,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
caddy_hetzner  | {"level":"info","ts":1771107262.6953595,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//[::1]:2019","//127.0.0.1:2019","//localhost:2019"]}
caddy_hetzner  | {"level":"info","ts":1771107262.6958938,"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}
caddy_hetzner  | {"level":"info","ts":1771107262.6959572,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
caddy_hetzner  | {"level":"info","ts":1771107262.6961644,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
caddy_hetzner  | {"level":"info","ts":1771107262.6963875,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details."}
caddy_hetzner  | {"level":"info","ts":1771107262.6966817,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
caddy_hetzner  | {"level":"warn","ts":1771107262.6968331,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
caddy_hetzner  | {"level":"warn","ts":1771107262.6968617,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
caddy_hetzner  | {"level":"info","ts":1771107262.6968808,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
caddy_hetzner  | {"level":"info","ts":1771107262.6969173,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["najan.de"]}
caddy_hetzner  | {"level":"info","ts":1771107262.6978922,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
caddy_hetzner  | {"level":"info","ts":1771107262.6979568,"msg":"serving initial configuration"}
caddy_hetzner  | {"level":"info","ts":1771107262.6997995,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"c5ea1217-d00a-4f3b-9f8e-9bfcc6349698","try_again":1771193662.6997967,"try_again_in":86399.999999098}
caddy_hetzner  | {"level":"info","ts":1771107262.700024,"logger":"tls","msg":"finished cleaning storage units"}
caddy_hetzner  | {"level":"info","ts":1771107262.7001064,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x15ab3e68f980"}
caddy_hetzner  | {"level":"info","ts":1771107262.7018473,"logger":"tls.obtain","msg":"acquiring lock","identifier":"najan.de"}
caddy_hetzner  | {"level":"info","ts":1771107262.7029355,"logger":"tls.obtain","msg":"lock acquired","identifier":"najan.de"}
caddy_hetzner  | {"level":"info","ts":1771107262.7031226,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"najan.de"}
caddy_hetzner  | {"level":"info","ts":1771107262.7043765,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["najan.de"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
caddy_hetzner  | {"level":"info","ts":1771107262.7044752,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["najan.de"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
caddy_hetzner  | {"level":"info","ts":1771107262.7045174,"logger":"tls.issuance.acme","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/3064071376","account_contact":[]}
caddy_hetzner  | {"level":"info","ts":1771107264.616377,"msg":"trying to solve challenge","identifier":"najan.de","challenge_type":"dns-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
caddy_hetzner  | {"level":"error","ts":1771107264.6971872,"msg":"cleaning up solver","identifier":"najan.de","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.najan.de\" (usually OK if presenting also failed)","stacktrace":"github.com/mholt/acmez/v3.(*Client).solveChallenges.func1\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:318\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:363\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.24.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.24.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.2/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.24.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.24.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.24.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.24.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.24.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.24.0/async.go:73"}
caddy_hetzner  | {"level":"error","ts":1771107264.9717643,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"najan.de","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[najan.de] solving challenges: presenting for challenge: adding temporary record for zone \"najan.de.\": Unauthorized (401) (order=https://acme-v02.api.letsencrypt.org/acme/order/3064071376/480350499376) (ca=https://acme-v02.api.letsencrypt.org/directory)"}
caddy_hetzner  | {"level":"error","ts":1771107264.971877,"logger":"tls.obtain","msg":"will retry","error":"[najan.de] Obtain: [najan.de] solving challenges: presenting for challenge: adding temporary record for zone \"najan.de.\": Unauthorized (401) (order=https://acme-v02.api.letsencrypt.org/acme/order/3064071376/480350499376) (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":2.268876445,"max_duration":2592000}

At the same time, the API TOKEN seems to work since using
curl -H "Authorization: Bearer [MY TOKEN]" https://api.hetzner.cloud/v1/zones" successfully shows my dns zone.

3. Caddy version:

docker-compose exec caddy caddy version

gives me:

snap install docker          # version 28.4.0, or
apt  install docker-compose  # version 1.29.2-1

4. How I installed and ran Caddy:

a. System environment:

I am trying to get caddy to work in a proxmox VM (ubuntu) with docker.

b. Command:

My Dockerfile looks like this:

FROM caddy:builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/hetzner/

FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Then I try to start caddy using this:

docker build -t caddy-hetzner .
docker compose down && docker compose up -d

c. Service/unit/compose file:

services:
  caddy:
    build: .
    container_name: caddy_hetzner
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./conf:/config
      - ./site:/srv
      - caddy_data:/data
      - caddy_config:/config
    environment:
      - HETZNER_API_TOKEN=[MY TOKEN]
volumes:
  caddy_data:
  caddy_config:

d. My complete Caddy config:

najan.de {
   respond "Hello World"
   tls {
   dns hetzner[MY TOKEN]
   }
}

You’re using the wrong version of the module. GitHub - caddy-dns/hetzner: Caddy module: dns.providers.hetzner