Controlling specificity when using multiple hosts in a single instance

1. Caddy version (caddy version):

v2.2.0-rc.1 h1:ULGBB9efRs8yG27IBOMLljhCa4iM1IluFTDxjm+Y8vE=

2. How I run Caddy:

a. System environment:

Debian 10, Docker 18.09.1, docker-compose 1.21.0

b. Command:

caddy run --config /etc/caddy/Caddyfile --adapter caddyfile

c. Service/unit/compose file:

Not relevant

d. My complete Caddyfile or JSON config:

{
	on_demand_tls {
		ask http://backend/check_domain
	}
}

www.{$API_URL} {
	redir https://{$API_URL}{uri} permanent
}

{$API_URL} {
	log {
		output file /var/log/api.{$API_URL}.log
	}

	root * /app/dashboard
	try_files {path} /index.html
	file_server

	encode zstd gzip
}

(web) {
	log {
		output file /var/log/web.log
	}

	root * /app/web
	try_files {path} /index.html
	file_server

	encode zstd gzip
}

*.{$API_URL} {
	import web
}

:443 {
	import web

	tls {
		on_demand
	}
}

With API_URL=example.com.

3. The problem I’m having:

When I hit https://test.example.com, I want Caddy to serve the *.{$API_URL} block, for which it already has acquired an SSL certificate. However, according to the logs, it starts acquiring a new certificate for that domain, which I believe is because it is actually serving the :443 block. This is an issue because it makes me hit the Let’s Encrypt rate limit quickly.

4. Error messages and/or full log output:

proxy_1     | {"level":"info","ts":1607375860.0716696,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
proxy_1     | {"level":"info","ts":1607375860.0769188,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["[::1]:2019","127.0.0.1:2019","localhost:2019"]}
proxy_1     | {"level":"info","ts":1607375860.0773702,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc000329260"}
proxy_1     | {"level":"info","ts":1607375860.0774772,"logger":"http","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}
proxy_1     | {"level":"info","ts":1607375860.0775027,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
proxy_1     | {"level":"info","ts":1607375860.0795758,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["www.example.com","example.com","*.example.com"]}
proxy_1     | {"level":"info","ts":1607375860.0804935,"logger":"tls","msg":"cleaned up storage units"}
proxy_1     | {"level":"info","ts":1607375860.0886722,"msg":"autosaved config","file":"/config/caddy/autosave.json"}
proxy_1     | {"level":"info","ts":1607375860.088687,"msg":"serving initial configuration"}
proxy_1     | {"level":"info","ts":1607375873.141258,"logger":"tls.on_demand","msg":"obtaining new certificate","server_name":"test.example.com"}
proxy_1     | {"level":"info","ts":1607375873.1415548,"logger":"tls.obtain","msg":"acquiring lock","identifier":"test.example.com"}
proxy_1     | {"level":"info","ts":1607375873.1417031,"logger":"tls.obtain","msg":"lock acquired","identifier":"test.example.com"}
proxy_1     | {"level":"info","ts":1607375873.1422827,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["test.example.com"]}
proxy_1     | {"level":"info","ts":1607375873.142294,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["test.example.com"]}
proxy_1     | {"level":"info","ts":1607375874.2488353,"logger":"tls.issuance.acme.acme_client","msg":"trying to solve challenge","identifier":"test.example.com","challenge_type":"tls-alpn-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
proxy_1     | 2020/12/07 21:17:54 http: TLS handshake error from 127.0.0.1:43656: EOF
proxy_1     | {"level":"info","ts":1607375874.4623997,"logger":"tls","msg":"served key authentication certificate","server_name":"test.example.com","challenge":"tls-alpn-01","remote":"192.168.0.1:43694"}
proxy_1     | {"level":"info","ts":1607375874.5954278,"logger":"tls","msg":"served key authentication certificate","server_name":"test.example.com","challenge":"tls-alpn-01","remote":"192.168.0.1:43696"}
proxy_1     | {"level":"info","ts":1607375874.7121022,"logger":"tls","msg":"served key authentication certificate","server_name":"test.example.com","challenge":"tls-alpn-01","remote":"192.168.0.1:43698"}
proxy_1     | {"level":"info","ts":1607375877.726885,"logger":"tls","msg":"served key authentication certificate","server_name":"test.example.com","challenge":"tls-alpn-01","remote":"192.168.0.1:43700"}
proxy_1     | {"level":"info","ts":1607375884.5793495,"logger":"tls.issuance.acme.acme_client","msg":"validations succeeded; finalizing order","order":"https://acme-v02.api.letsencrypt.org/acme/order/105431595/6619314109"}
proxy_1     | {"level":"info","ts":1607375887.8825824,"logger":"tls.issuance.acme.acme_client","msg":"successfully downloaded available certificate chains","count":2,"first_url":"https://acme-v02.api.letsencrypt.org/acme/cert/030f8679d7958af7a8bc73b94ecb46e923b2"}
proxy_1     | {"level":"info","ts":1607375887.8829277,"logger":"tls.obtain","msg":"certificate obtained successfully","identifier":"test.example.com"}
proxy_1     | {"level":"info","ts":1607375887.882938,"logger":"tls.obtain","msg":"releasing lock","identifier":"test.example.com"}

5. What I already tried:

To be fair, nothing. Documentation is not really extensive on specificity of host matches. I would expect, however, a wildcard host to be more specific than a port host.

6. Links to relevant resources:

Documentation on addresses which was my first resource.

Please upgrade to v2.2.1, there were some important fixes since RC1.

How do you know this? You don’t have anything in your config that would allow Caddy to fetch a wildcard certificate for your domain.

You would need to use the DNS challenge to use have Caddy fetch a wildcard certificate:

This line:

proxy_1     | {"level":"info","ts":1607375860.0795758,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["www.example.com","example.com","*.example.com"]}

Makes me think that *.example.com will get a certificate of its own.

Right - that just means that those were the domains collected during config parsing that may have certificates managed. But the decision about fetching wildcard certificates happens later.

Like I said, the DNS challenge is necessary for fetching wildcard certificates. This is a limitation imposed by Let’s Encrypt, because it doesn’t make sense to use the HTTP or ALPN challenge to verify that a server owns the domain, since there’s no way to exhaustively verify that the server manages all subdomains using those challenges.

That said, I tried caddy adapt-ing your config to see what it looks like, and I do think there’s an ordering bug in the Caddyfile adapter, because the subroutes (and their host matchers) appeared in this order:

  • www.example.com
  • example.com
  • no matcher
  • *.example.com

So I think wildcard host matchers are being ordered after those with no matcher. The logic for sorting there is pretty complex, so I asked @matt to look into it when he has the time.

That said, I don’t think what you’re trying to do is actually correct; like I said you’ll need to use the DNS challenge if you want Caddy to get a certificate for *.example.com up-front. Otherwise, Caddy will need to fetch individual certificates on demand. But also on-demand certificates are valid for 90 days and Caddy stores them, so you would only hit rate limits if you have a ton of domains you need to issue certificates for in a short period of time. You can use the burst and interval on-demand global options to set limits on that, along with the ask to only allow trusted domains.

Thanks, I looked into that. I now set

on_demand_tls {
	ask http://backend:8074/check_domain
}

in global configuration and

tls {
    dns cloudflare {$CLOUDFLARE_TLS_TOKEN}
}

in the wildcard host block, compiling Caddy with the caddy-dns/cloudflare plugin.

However,

you would only hit rate limits if you have a ton of domains you need to issue certificates for in a short period of time

this is a possibility for me. I didn’t try to set up a port to the wildcard domain, which I guess should at least put it in the same specificity as the port-only host matcher, because it was not clear to me whether using domain:443 was equivalent to using https://domain which would prevent automatic HTTPS, but I will investigate.

Then setting the burst limit for on-demand would help there :wink:

This topic was automatically closed after 30 days. New replies are no longer allowed.