I want to use Caddy as a reverse proxy for my system. The system contains a backend running on localhost:8080 to which all traffic is redirected. The proxy listens for HTTP traffic on :80 and for HTTPS traffic on :443. In the system, there's also a storage (a Vault, Caddy was built with the Vault module) on which the certificates are stored.
I don't want Caddy to manage the certificates. The certs creation, renewal and deletion is done by another process. The only feature of Caddy I'm interested in is its ability to lazy load the certificates, I have a lot of them and I don't want to use a proxy that preloads them, it takes too much time.
When Caddy receives traffic on :443, it checks in its storage whether it has a certificate for the requested SNI. If it does, the TLS connection is established. If it doesn't, the connection is dropped, the client gets an HTTP error.
However, I'm not sure Caddy is able to do it. When I allow Caddy to manage certificates by providing an ACME configuration, it works. When I have certificates already and I only configure the storage, each HTTPS request leads to a failure.
As an example, here's a working Caddyfile, cert management is made by Caddy using ACME:
{ on_demand_tls { ask http://my.checking.service/check } storage vault { addresses "https://my.vault.instance" token_path "/etc/caddy/vault_token" secrets_mount_path secret secrets_path_prefix caddy/certificates }}http://:80 { reverse_proxy :8080}https://:443 { tls { on_demand issuer acme { dir https://my.vault.instance/v1/pki_int/acme/directory timeout 10m disable_tlsalpn_challenge } } reverse_proxy :8080}Assuming http://my.checking.service/check allows me to create a certificate for https://my-domain.local, the result is that I get a .crt& .key for my-domain.local in the Vault instance. Further requests to https://my-domain.local don't lead to a certificate renewal as it already exists, Caddy just uses the one that has been generated, as expected.
Now I want to stop Caddy from issuing certificates, as mentioned, this is done by another process. I tried multiple directives, none of them was relevant for this goal and I find the documentation quite limited.
Here is my Caddyfile so far:
{ auto_https off storage vault { addresses "https://my.vault.instance" token_path "/etc/caddy/vault_token" secrets_mount_path secret secrets_path_prefix caddy/certificates } debug}http://:80 { reverse_proxy :8080}https://:443 { tls { # load caddy/certificates # This directive is of no use since the certs are not on the local storage, but I also tested with local storage, and it doesn't load the certs it has access to anyway ¯\_(ツ)_/¯ client_auth { mode require_and_verify # trust_pool file caddy/pki/authorities/local/root.crt # Same comment as for the `load` directive } } reverse_proxy :8080}Caddy is running on localhost, on the same computer I run the following command:
curl -vvLkI https://my-domain.local --connect-to 'my-domain.local:443:127.0.0.1'
And I get this error:
* Connecting to hostname: 127.0.0.1* Trying 127.0.0.1:443...* Connected to (nil) (127.0.0.1) port 443 (#0)* ALPN: offers h2,http/1.1* TLSv1.3 (OUT), TLS handshake, Client hello (1):* TLSv1.3 (IN), TLS alert, internal error (592):* OpenSSL/3.0.17: error:0A000438:SSL routines::tlsv1 alert internal error* Closing connection 0curl: (35) OpenSSL/3.0.17: error:0A000438:SSL routines::tlsv1 alert internal errorCaddy can't find a certificate for this domain:
DEBUG events event {"name": "tls_get_certificate", "id": "ea472fdf-59d8-43ec-90f8-7215a0ca057d", "origin": "tls", "data": {"client_hello":{"CipherSuites":[4866,4867,4865,49196,49200,159,52393,52392,52394,49195,49199,158,49188,49192,107,49187,49191,103,49162,49172,57,49161,49171,51,157,156,61,60,53,47,255],"ServerName":"my-domain.local","SupportedCurves":[29,23,30,25,24,256,257,258,259,260],"SupportedPoints":"AAEC","SignatureSchemes":[1027,1283,1539,2055,2056,2057,2058,2059,2052,2053,2054,1025,1281,1537,771,769,770,1026,1282,1538],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771,770,769],"RemoteAddr":{"IP":"127.0.0.1","Port":32874,"Zone":""},"LocalAddr":{"IP":"127.0.0.1","Port":443,"Zone":""}}}}DEBUG tls.handshake no matching certificates and no custom selection logic {"identifier": "my-domain.local"}DEBUG tls.handshake no matching certificates and no custom selection logic {"identifier": "*.local"}DEBUG tls.handshake no matching certificates and no custom selection logic {"identifier": "*.*"}DEBUG tls.handshake no certificate matching TLS ClientHello {"remote_ip": "127.0.0.1", "remote_port": "32874", "server_name": "my-domain.local", "remote": "127.0.0.1:32874", "identifier": "my-domain.local", "cipher_suites": [4866, 4867, 4865, 49196, 49200, 159, 52393, 52392, 52394, 49195, 49199, 158, 49188, 49192, 107, 49187, 49191, 103, 49162, 49172, 57, 49161, 49171, 51, 157, 156, 61, 60, 53, 47, 255], "cert_cache_fill": 0, "load_or_obtain_if_necessary": true, "on_demand": false}DEBUG http.stdlib http: TLS handshake error from 127.0.0.1:32874: no certificate available for 'my-domain.local'Long story short, I need a reverse proxy that can:
- Share its certificate storage across multiple instances (in this case, Vault)
- Handle a lot of certificates (in this case lazy loading)
- Match certificates based on SNI
- Strict SNI, TLS connections for a SNI that doesn't match a certificate are dropped
Is there a configuration that could make it or should I consider switching to another proxy?