Problems mixing automatic and on demand TLS

1. The problem I’m having:

I am trying to configure caddy to work with on-demand TLS for a set of wildcard subdomains AND also with normal automatic certificates for a specific subdomain known in advance. In other words I want one specific hostname (sub.gpp.garden) to use the default automatic HTTPS, and I want every other hostname (e.g. gpp.gpp.garden) to use on_demand_tls. I have defined an ask endpoint, and the on-demand endpoints actually work great! The issue is the endpoint that DOESN’T use on-demand tls, i.e sub.gpp.garden, see Caddyfile at the bottom

Actually while writing this I tried the solution in this post [On Demand TLS for the front-end only], where instead of using wildcards they matched https:// , and that actually worked! So I guess my issue is basically solved, but I’d be interested to know why exactly the other way doesn’t work (unless using a catch-all like https:// can also lead to issues)

2. Error messages and/or full log output:

> curl -vL https://sub.gpp.garden
* Host sub.gpp.garden:443 was resolved.
* IPv6: (none)
* IPv4: 157.180.126.12
*   Trying 157.180.126.12:443...
* Connected to sub.gpp.garden (157.180.126.12) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS alert, internal error (592):
* OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
* Closing connection
curl: (35) OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
{"level":"info","ts":1759768416.7432668,"msg":"maxprocs: Leaving GOMAXPROCS=2: CPU quota undefined"}
{"level":"info","ts":1759768416.7435856,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":3605007974,"previous":9223372036854775807}
{"level":"info","ts":1759768416.7436604,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
{"level":"info","ts":1759768416.7462163,"msg":"adapted config to JSON","adapter":"caddyfile"}
{"level":"info","ts":1759768416.7485256,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1759768416.7491105,"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}
{"level":"info","ts":1759768416.7492373,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1759768416.7491288,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0005f0980"}
{"level":"debug","ts":1759768416.7494326,"logger":"http.auto_https","msg":"adjusted config","tls":{"automation":{"policies":[{"subjects":["sub.gpp.garden"]},{"subjects":["*.gpp.garden","gpp.garden"],"on_demand":true},{}],"on_demand":{}}},"http":{"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"body":"{http.request.host}","handler":"static_response"}]}]}],"terminal":true},{"handle":[{"handler":"subroute","routes":[{"handle":[{"body":"{http.request.host}","handler":"static_response"}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
{"level":"debug","ts":1759768416.7498596,"logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":false}
{"level":"info","ts":1759768416.7499595,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"info","ts":1759768416.7502544,"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."}
{"level":"info","ts":1759768416.7505631,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"debug","ts":1759768416.7507508,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
{"level":"warn","ts":1759768416.7508128,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
{"level":"warn","ts":1759768416.7508342,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
{"level":"info","ts":1759768416.750851,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"info","ts":1759768416.750908,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["sub.gpp.garden","*.gpp.garden","gpp.garden"]}
{"level":"debug","ts":1759768416.7510214,"logger":"events","msg":"event","name":"started","id":"656b31e9-5ba4-451e-9efa-e61620e0117a","origin":"","data":null}
{"level":"info","ts":1759768416.75142,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1759768416.751475,"msg":"serving initial configuration"}
{"level":"info","ts":1759768416.753164,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"95f3e742-1cd4-4ede-ab6d-20b444c5c009","try_again":1759854816.7531605,"try_again_in":86399.999999438}
{"level":"info","ts":1759768416.7532907,"logger":"tls","msg":"finished cleaning storage units"}
{"level":"debug","ts":1759768424.558122,"logger":"events","msg":"event","name":"tls_get_certificate","id":"899e5cc2-ec2a-4112-8e5c-51b8f73764a9","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"ServerName":"sub.gpp.garden","SupportedCurves":[4588,29,23,24,25,256,257],"SupportedPoints":"AA==","SignatureSchemes":[1027,1283,1539,2052,2053,2054,1025,1281,1537,515,513],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"RemoteAddr":{"IP":"45.92.33.168","Port":39633,"Zone":""},"LocalAddr":{"IP":"157.180.126.12","Port":443,"Zone":""}}}}
{"level":"debug","ts":1759768424.5583076,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"sub.gpp.garden"}
{"level":"debug","ts":1759768424.558315,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.gpp.garden"}
{"level":"debug","ts":1759768424.5583181,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.garden"}
{"level":"debug","ts":1759768424.5583212,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*"}
{"level":"debug","ts":1759768424.5583634,"logger":"tls.handshake","msg":"no certificate matching TLS ClientHello","remote_ip":"45.92.33.168","remote_port":"39633","server_name":"sub.gpp.garden","remote":"45.92.33.168:39633","identifier":"sub.gpp.garden","cipher_suites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"cert_cache_fill":0,"load_or_obtain_if_necessary":true,"on_demand":false}
{"level":"debug","ts":1759768424.5584762,"logger":"http.stdlib","msg":"http: TLS handshake error from 45.92.33.168:39633: no certificate available for 'sub.gpp.garden'"}
{"level":"debug","ts":1759768424.8022497,"logger":"events","msg":"event","name":"tls_get_certificate","id":"3afc1e38-8f34-44f5-aac2-cdddb27972af","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"ServerName":"sub.gpp.garden","SupportedCurves":[29,23,24,25,256,257],"SupportedPoints":"AA==","SignatureSchemes":[1027,1283,1539,2052,2053,2054,1025,1281,1537,515,513],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771],"RemoteAddr":{"IP":"45.92.33.168","Port":59530,"Zone":""},"LocalAddr":{"IP":"157.180.126.12","Port":443,"Zone":""}}}}
{"level":"debug","ts":1759768424.802429,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"sub.gpp.garden"}
{"level":"debug","ts":1759768424.8024473,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.gpp.garden"}
{"level":"debug","ts":1759768424.8024783,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.garden"}
{"level":"debug","ts":1759768424.8024883,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*"}
{"level":"debug","ts":1759768424.8025143,"logger":"tls.handshake","msg":"no certificate matching TLS ClientHello","remote_ip":"45.92.33.168","remote_port":"59530","server_name":"sub.gpp.garden","remote":"45.92.33.168:59530","identifier":"sub.gpp.garden","cipher_suites":[4865,4867,4866,49195,49199,52393,52392,49196,49200,49162,49161,49171,49172,156,157,47,53],"cert_cache_fill":0,"load_or_obtain_if_necessary":true,"on_demand":false}
{"level":"debug","ts":1759768424.802716,"logger":"http.stdlib","msg":"http: TLS handshake error from 45.92.33.168:59530: no certificate available for 'sub.gpp.garden'"}

3. Caddy version:

4. How I installed and ran Caddy:

a. System environment:

Running Ubuntu 24.04.3 with Docker:

version: '3.9'
services:
  caddy:
    container_name: caddy
    image: caddy:2
    network_mode: host
    restart: unless-stopped
    volumes:
      - type: bind
        source: ./caddy/data
        target: /data
      - type: bind
        source: ./caddy/etc/caddy
        target: /etc/caddy

d. My complete Caddy config:

{
        email my@email.com
        on_demand_tls {
                ask http://localhost:3000/tls-check
        }
}

sub.gpp.garden {
        respond "A response: {http.request.host}"
}

*.gpp.garden, gpp.garden {
        tls {
                on_demand
        }
        respond "A different response: {http.request.host}"
}
1 Like

You didn’t mention which version of Caddy you’re using, so I’ll assume it’s somewhere in the 2.10.x range.

Starting with version 2.10.0, if there’s a wildcard domain configured, its wildcard certificate will be used for all matching subdomains. From the v2.10.0 release notes:

  • Wildcards used by default: Previously, Caddy would obtain individual certificates for every domain in your config literally; now wildcards, if present, will be utilized for subdomains, rather than obtaining individual certificates. This change was motivated by the novel possibility for subdomain privacy afforded by ECH. It can be overridden with tls force_automate in the Caddyfile. The experimental auto_https prefer_wildcard option has been removed.

Here’s what your Caddyfile could look like:

{
        email my@email.com
        on_demand_tls {
                ask http://localhost:3000/tls-check
        }
}

sub.gpp.garden {
        tls force_automate
        respond "A response: {http.request.host}"
}

*.gpp.garden, gpp.garden {
        tls {
                on_demand
        }
        respond "A different response: {http.request.host}"
}

It looks like this detail isn’t currently covered in the official tls documentation, but you can find it both in the release notes and in the code:

3 Likes

Thank you, that worked flawlessly!

1 Like