caddy icon indicating copy to clipboard operation
caddy copied to clipboard

Feature request: Caddyfile syntax to automate certificate without adding corresponding HTTPS route

Open bcat opened this issue 5 months ago • 2 comments

This is a bit of a weird request, and I won't be at all offended if it gets closed as "working as intended", but I think it'd be a small quality of life improvement if the Caddyfile syntax allowed certificate automation to be configured without adding an HTTPS site. Two use cases:

  1. As of Caddy 2.10, wildcard certs are optimistically used for non-wildcard site blocks by default, which is awesome. But there's still a question of how to provision the wildcard cert in the first place. It seems to require a "dummy" site like so:

    *.example.com {
    }
    
    foo.example.com {
       respond "Real site"
    }
    

    This is easy enough, but means that Caddy will also serve empty-but-valid HTTP responses when domain names without corresponding site blocks (e.g., bar.example.com) are pointed at the Caddy host. I guess this is ultimately not a big deal (and someone can probably put an abort in the wildcard site block if it really bothers them), but it seems like it's conflating two concepts that, when it comes to wildcard certs, should really be separate.

  2. When caddy-l4 terminates SNI-matched TLS, it appears to use Caddy's standard cert-selection logic, including preferring wildcard certs. Again, this is awesome! (And I'm personally using it now to take advantage of Caddy's excellent cert automation for non-HTTP services like Valkey.) But, again, actually provisioning the certificate in the Caddyfile syntax seems to require an empty site block---and thus an HTTPS server---even if the Caddy instance is only used for non-HTTPS traffic.

It looks like the underlying JSON syntax completely decouples cert automation from servers, and I think would be nice to have a Caddyfile option to enable automation for additional certificates. Maybe something like additional_certs <name>... in the global options block?

bcat avatar Jul 13 '25 22:07 bcat

So basically you want the above config to use a wildcard cert, just without:

*.example.com {
}

?

🤔

mholt avatar Jul 14 '25 21:07 mholt

Basically, and without the corresponding route in the HTTP app. Right now, this Caddyfile

{
        email [email protected]
}
*.example.com {
}
foo.example.com {
        respond "Hello world"
}

turns into this JSON:

Current JSON with extraneous wildcard site
{
        "apps": {
                "http": {
                        "servers": {
                                "srv0": {
                                        "listen": [
                                                ":443"
                                        ],
                                        "routes": [
                                                {
                                                        "match": [
                                                                {
                                                                        "host": [
                                                                                "foo.example.com"
                                                                        ]
                                                                }
                                                        ],
                                                        "handle": [
                                                                {
                                                                        "handler": "subroute",
                                                                        "routes": [
                                                                                {
                                                                                        "handle": [
                                                                                                {
                                                                                                        "body": "Hello world",
                                                                                                        "handler": "static_response"
                                                                                                }
                                                                                        ]
                                                                                }
                                                                        ]
                                                                }
                                                        ],
                                                        "terminal": true
                                                },
                                                {
                                                        "match": [
                                                                {
                                                                        "host": [
                                                                                "*.example.com"
                                                                        ]
                                                                }
                                                        ],
                                                        "terminal": true
                                                }
                                        ]
                                }
                        }
                },
                "tls": {
                        "automation": {
                                "policies": [
                                        {
                                                "subjects": [
                                                        "foo.example.com",
                                                        "*.example.com"
                                                ],
                                                "issuers": [
                                                        {
                                                                "email": "[email protected]",
                                                                "module": "acme"
                                                        },
                                                        {
                                                                "ca": "https://acme.zerossl.com/v2/DV90",
                                                                "email": "[email protected]",
                                                                "module": "acme"
                                                        }
                                                ]
                                        }
                                ]
                        }
                }
        }
}

I am suggesting something like

{
        email [email protected]
        additional_certs *.example.com
}
foo.example.com {
        respond "Hello world"
}

would give this JSON

Suggested JSON with no extra site
{
        "apps": {
                "http": {
                        "servers": {
                                "srv0": {
                                        "listen": [
                                                ":443"
                                        ],
                                        "routes": [
                                                {
                                                        "match": [
                                                                {
                                                                        "host": [
                                                                                "foo.example.com"
                                                                        ]
                                                                }
                                                        ],
                                                        "handle": [
                                                                {
                                                                        "handler": "subroute",
                                                                        "routes": [
                                                                                {
                                                                                        "handle": [
                                                                                                {
                                                                                                        "body": "Hello world",
                                                                                                        "handler": "static_response"
                                                                                                }
                                                                                        ]
                                                                                }
                                                                        ]
                                                                }
                                                        ],
                                                        "terminal": true
                                                }
                                        ]
                                }
                        }
                },
                "tls": {
                        "automation": {
                                "policies": [
                                        {
                                                "subjects": [
                                                        "foo.example.com",
                                                        "*.example.com"
                                                ],
                                                "issuers": [
                                                        {
                                                                "email": "[email protected]",
                                                                "module": "acme"
                                                        },
                                                        {
                                                                "ca": "https://acme.zerossl.com/v2/DV90",
                                                                "email": "[email protected]",
                                                                "module": "acme"
                                                        }
                                                ]
                                        }
                                ]
                        }
                }
        }
}

So Caddy would automate the wildcard cert, but not serve HTTP responses there. Having made this example, I acknowledge it's a minor difference, and maybe more about configuration cleanliness than anything else.


IMO, this would be even more useful with the L4 app. I have a Caddy instance that only proxies non-HTTP traffic over caddy-l4, so the basic config is like so:

{
        acme_dns cloudflare {$CF_API_TOKEN}
        email [email protected]
        layer4 {
                :1234 {
                        @bar tls sni bar.example.com
                        route @bar {
                                tls
                                proxy localhost:5678
                        }
                }
        }
}

But in that config, Caddy doesn't actually provision a cert for bar.example.com. I have to add a dummy HTTP site for it to do so, even though this Caddy otherwise needs no HTTP server at all. If I could just write additional_certs bar.example.com, I think that'd be a bit nicer.

bcat avatar Jul 15 '25 01:07 bcat