Catch-all block in case no other domain block matches

1. Caddy version (caddy version):

2.3.0

2. How I run Caddy:

a. System environment:

Docker-based, base OS is Debian.

c. Service/unit/compose file:

services:
  caddy:
    build: .
    container_name: caddy
    networks:
      - web_proxy
    ports:
      - 80:80
      - 443:443
    restart: always
    volumes:
      - /etc/docker_services/caddy/conf:/etc/caddy
      - /srv/caddy:/data
      - /srv/web:/srv
      - /etc/letsencrypt:/certs

networks:
  web_proxy:
    external: true

d. My complete Caddyfile or JSON config:

example.com {
  file_server
  root * /srv/example.com
}
example.org {
  file_server
  root * /srv/example.org
  tls /certs/live/example.org/fullchain.pem /certs/live/example.org/privkey.pem
}
:443 {
  redir https://example.org/owa
  tls internal {
    on_demand
  }
}

3. The problem I’m having:

My Caddy instance serves multiple domains, some with externally provided TLS certs and mounted into the container, others relying on ACME via Caddy. In case someone connects directly to the public IP address, I would like for Caddy to serve a default redirection to one of the other domains. However, with the configuration above the matcher :443 seems to overwrite the TLS config for example.com, but not the one for example.org. More specifically, with the config above (and real domain names),

  • example.org serves the correct site and uses the external TLS certificate
  • example.com also serves the correct site, but doesn’t rely on ACME anymore and suddenly also uses a certificate generated from tls internal
  • https://<IP> redirects as expected and uses an internal cert, as intended.

5. What I already tried:

As an alternative, I tried to specify just the external IP of the host:

1.2.3.4 {
  redir https://example.org/owa
  tls internal
}

However, Caddy doesn’t like that:

{"level":"debug","ts":1616175933.7395344,"logger":"http.stdlib","msg":"http: TLS handshake error from 1.2.3.4:55544: no certificate available for '172.18.0.3'"}

172.18.0.3 being the internal docker IP and subject to change. It does work when adding that one as a second IP, though:

1.2.3.4, 172.18.0.3 {
  redir https://example.org/owa
  tls internal
}

But since that internal IP is unpredictable, I don’t like to fix that in my config. Is there some sort of generic IP catch-all matcher?

Just for the sake of completion: Just using the internal IP as a matcher returns a HTTP 200 without any content when accessing the site via IP directly. The debug log is empty in that case. Why is that?

172.18.0.3 {
  redir https://example.org/owa
  tls internal
}

$ curl -vk https://1.2.3.4
...
< HTTP/2 200
< server: Caddy
< content-length: 0
...

If you run caddy adapt --pretty on your config, you should see the order in which your site blocks are sorted.

There was a known issue that has been fixed relating to this, the sorting in the Caddyfile adapter had a bug:

If you build from master or from that commit edb362aa96bd8e79adfaca44fbd9f9ce5bff778d you can get the fix now.

I think this is because Caddy had a certificate in storage for 1.2.3.4 already (because you previously configured it) but now has no matcher, so no route matches and it falls through unhandled, which results in an empty 200 response.

Thanks for the reply!
I built caddy from master, but that didn’t change anything. I think my issue might be different, because in my case only TLS settings are inconsistent, while the actual content served for the different domain’s blocks is correct. The problem persists: with the following Caddyfile, example.com will offer an internal TLS certificate instead of one acquired via ACME. example.org will correctly use the supplied external certificates and the catch all block will be served with an internal cert when accessing the host directly via its IP address.

example.com {
  file_server
  root * /srv/example.com
}
example.org {
  file_server
  root * /srv/example.org
  tls /certs/live/example.org/fullchain.pem /certs/live/example.org/privkey.pem
}
:443 {
  redir https://example.org/owa
  tls internal {
    on_demand
  }
}

Any further ideas? Did I hit a bug here?

PS: If I specifically add a tls <email> instruction to the example.com block, ACME will work. Is that intended behaviour (:443 dictating a default for the other blocks)?

If you look in Caddy’s cert storage, try deleting the certificate for example.com that was from the internal issuer. I think it’s the same issue as I described here:

If Caddy already has a certificate in storage for that domain, it won’t try to issue a new one. That’s not a bug either, because Caddy supports both Let’s Encrypt and ZeroSSL as default issuers, and it wouldn’t make sense to fetch a cert for the other one if it already has one. There’s no inherent differentiation that would prioritize some issuers over others.

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