I failed to enable ECH on caddy

1. The problem I’m having:

According to the documentation, enabling ECH in Caddy seems straightforward: set env.CLOUDFLARE_API_TOKEN and add two lines in the global block:

{
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
ech ech.example.net
}

As I understand it, ECH is just an encryption/decryption mechanism, and the domain name after ech can be anything – even just a single character.

However, after I configured this, the nightmare began. Error logs like the following appeared:

Caddy kept trying to obtain certificates even when I set auto_https disable_certs to disable automatic certificate management. I then switched to a domain I actually control. After a while, Caddy generated .crt, .key, config.bin, and key.bin files, and I saw "2.ech=“AEn+DQ…” records appear in Cloudflare DNS. However, when I test with curl --ech and inspect packets with Wireshark, ECH does not appear to be actually working.

What could be going wrong?

2. Error messages and/or full log output:

Jul 18 17:27:13 : {"level":"info","ts":1784366833.375879,"logger":"http","msg":"waiting on internal rate limiter","identifiers":["www.courant.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}Jul 18 17:27:13 : {"level":"info","ts":1784366833.3765242,"logger":"http","msg":"done waiting on internal rate limiter","identifiers":["www.courant.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}Jul 18 17:27:13 : {"level":"info","ts":1784366833.3765945,"logger":"http","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/3545384985","account_contact":}Jul 18 17:27:14 : {"level":"info","ts":1784366834.6525803,"logger":"http.acme_client","msg":"trying to solve challenge","identifier":"www.courant.com","challenge_type":"dns-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}Jul 18 17:27:15 : {"level":"error","ts":1784366835.3726997,"logger":"http.acme_client","msg":"cleaning up solver","identifier":"www.courant.com","challenge_type":"dns-01","error":"no memory of presenting a DNS record for "_acme-challenge.www.courant.com" (usually OK if presenting also failed)"}Jul 18 17:27:15 : {"level":"error","ts":1784366835.5886862,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"www.courant.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[www.courant.com] solving challenges: presenting for challenge: adding temporary record for zone "courant.com.": expected 1 zone, got 0 for courant.com. (order=https://acme-v02.api.letsencrypt.org/acme/order/3545384985/533675824255) (ca=https://acme-v02.api.letsencrypt.org/directory)"}Jul 18 17:27:15 : {"level":"error","ts":1784366835.588895,"logger":"tls.obtain","msg":"will retry","error":"[www.courant.com] Obtain: [www.courant.com] solving challenges: presenting for challenge: adding temporary record for zone "courant.com.": expected 1 zone, got 0 for courant.com. (order=https://acme-v02.api.letsencrypt.org/acme/order/3545384985/533675824255) (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":2.213651226,"max_duration":2592000}

3. Caddy version:

v2.11.4 h1:XKxkMTgNSizEvKG6QHue6cAsFOteU2qA61w2tKkCWi0=

4. How I installed and ran Caddy:

xcaddy build --with GitHub - caddy-dns/cloudflare: Caddy module: dns.providers.cloudflare · GitHub

a. System environment:

ubuntu 26.04 x86

caddy list-modules->dns.providers.cloudflare

b. Command:

curl -v --ech hard https://mydomain
* Host mydomain:443 was resolved.
* IPv6: 2400:xxxxx
* IPv4: 23.xx.xxx.xx
* HTTPS-RR: -
*   Trying [2400:xxxxx]:443...
*   Trying 23.xx.xxx.xx:443...
* ECH: requested but no ECHConfig available
* closing connection #0
curl: (35) SSL connect error

c. Service/unit/compose file:

PASTE OVER THIS HERE IN THIS CODE BLOCK.
Please ensure it looks nice.

d. My complete Caddy config:

{
auto_https disable_certs    

# https://acme-staging-v02.api.letsencrypt.org/directory
# dns cloudflare {env.CLOUDFLARE_API_TOKEN}
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
ech www.courant.com

}

www.nodisk.xyz, nodisk.xyz {
    tls /etc/nginx/ssl/***.crt /etc/nginx/ssl/###.key {
        resolvers 1.1.1.1
    }

    header {
        X-Frame-Options         "SAMEORIGIN"
        X-Content-Type-Options  "nosniff"
        Referrer-Policy         "strict-origin-when-cross-origin"
        -Server
    }

    root * /var/www/html
    file_server

    route /portal/v2/fetch* {
        @not_allowed {
            not method GET POST
        }
        respond @not_allowed 403

        reverse_proxy 127.0.0.1:9056 {
            header_up Host {host}
            header_up X-Real-IP {remote_host}
            flush_interval -1
            transport http {
                read_timeout 10m
                write_timeout 10m
            }
            header_down -X-Powered-By
            header_down -Server
        }
    }
    log {
output file /var/log/caddy/system.log {
roll_size 20mb
roll_keep 5
roll_keep_for 6h
}
format console
level debug
}
}

:80 {
    respond 404
}

5. Links to relevant resources:

Type here

Yes it can be anything, however – from our ECH documentation:

This name can be anything, but your server must be authoritative for the public name because Caddy will obtain a certificate for it.

Need the read the whole sentence.

auto_https disable_certs doesn’t override your ECH configuration.

If I understand you correctly, “ech.example.net” must be a domain I can control.
I followed the steps, and the ECH files (config.bin, key.bin) were successfully generated.

I deleted the initial HTTPS record from the Cloudflare dashboard.

I haven’t added it back since; the logs show no errors, and the CLOUDFLARE_API_TOKEN is valid and readable.

How can I get Caddy to re-add the record?

Does your DNS zone meet these criteria?

Caddy will only create an HTTPS record for a domain if there is already a record for that domain. This prevents breaking DNS lookups for a subdomain that may be covered by a wildcard. Ensure that your sites have at least an A/AAAA record pointing to your server. If you only use a wildcard for DNS records, then the wildcard domain will need to appear in your Caddy config as well.

Caddy will not publish an HTTPS record for a domain that has a CNAME record.

(Thanks for using your actual domain names above – I would check for you but I’m running out the door rn)


Yes, I created A and AAAA records in Cloudflare pointing to the server IP; no CNAME records, not a wildcard domain.

I manually deleted /var/lib/caddy/.local/share/caddy/ech/configs/. restarting Caddy

running sudo journalctl -u caddy -f | grep -i ech -->produced no output

running sudo journalctl -u caddy -n 50 --no-pager -->no errors