Use existing certificates in on-demand tls

1. The problem I’m having:

We manage custom domains and I am moving from in-house certificate management to Caddy. I want to use existing certificates and let Caddy do the renewals. For the new domains, Caddy can do complete certificate management (issuing + renewal).
What I have done is copied the existing certs pem and private key pem in Caddy’s storage location and named the keys in the format understood by Caddy.
However, Caddy doesn’t seem to be recognizing the certificates and provisioning new certificates.

I am trying to use existing certificates for existing domains, rather than re-creating new certificates for them when the request is routed through Caddy, so that I can avoid hitting Letsencrypt rate limits(300 certs in 3 hours)

2. Error messages and/or full log output:

On container start - loading certificate into cache

{"level":"info","ts":1681196084.2337275,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"https"}
{"level":"info","ts":1681196084.234672,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"info","ts":1681196084.234672,"logger":"tls","msg":"cleaning storage unit","description":"&{3ns 60ns  0xc000351520}"}
{"level":"debug","ts":1681196084.2347155,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'ocsp%'"}
{"level":"info","ts":1681196084.234737,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Receive-Buffer-Size for details."}
{"level":"debug","ts":1681196084.2347834,"logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":true}
{"level":"info","ts":1681196084.2347996,"logger":"http.log","msg":"server running","name":"https","protocols":["h1","h2","h3"]}
{"level":"debug","ts":1681196084.2348254,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
{"level":"info","ts":1681196084.23483,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"debug","ts":1681196084.2348452,"logger":"http","msg":"starting server loop","address":"[::]:9999","tls":false,"http3":false}
{"level":"info","ts":1681196084.2348526,"logger":"http.log","msg":"server running","name":"health-check","protocols":["h1","h2","h3"]}
{"level":"info","ts":1681196084.2349665,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1681196084.2349763,"msg":"serving initial configuration"}
{"level":"debug","ts":1681196084.2352035,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates%'"}
{"level":"debug","ts":1681196084.2452528,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.json%'"}
{"level":"debug","ts":1681196084.2456384,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.json%'"}
{"level":"debug","ts":1681196084.2460475,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.json%'"}
{"level":"debug","ts":1681196084.2475772,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.key%'"}
{"level":"debug","ts":1681196084.2479799,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.key%'"}
{"level":"debug","ts":1681196084.2483592,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.key%'"}
{"level":"debug","ts":1681196084.248756,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/
{"level":"debug","ts":1681196084.256117,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.crt%'"}
{"level":"debug","ts":1681196084.2565248,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.crt%'"}
{"level":"debug","ts":1681196084.2568374,"logger":"storage.mysql.sql","msg":"SELECT value FROM certmagic_data WHERE `key_hash` = dc16c872b8eef199641f76c16385949b"}
{"level":"debug","ts":1681196084.2575946,"logger":"storage.mysql.sql","msg":"select `key` from certmagic_data where `key` like 'certificates/acme-v02.api.letsencrypt.org-directory/maaiz.ptmn.com/maaiz.ptmn.com.crt%'"}

On hitting domain - not using the existing certificate

{"level":"debug","ts":1681196258.0756476,"logger":"events","msg":"event","name":"tls_get_certificate","id":"7e00e2db-0bf2-4012-834a-fe1386f40391","origin":"tls","dataError":"json: unsupported type: proxyproto.Validator"}
{"level":"debug","ts":1681196258.0757575,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"maaiz.ptmn.com"}
{"level":"debug","ts":1681196258.0757627,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.postmanlabs-beta.com"}
{"level":"debug","ts":1681196258.0757658,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.com"}
{"level":"debug","ts":1681196258.0757685,"logger":"tls.handshake","msg":"no matching certificates and no custom selection logic","identifier":"*.*.*"}
{"level":"debug","ts":1681196258.0757725,"logger":"tls.handshake","msg":"all external certificate managers yielded no certificates and no errors","remote_ip":"172.105.36.145","remote_port":"60842","sni":"maaiz.ptmn.com"}
{"level":"debug","ts":1681196258.0758367,"logger":"storage.mysql.sql","msg":"SELECT value FROM certmagic_data WHERE `key_hash` = 1b0f72132cbd71ce53d6800d0fac696d"}
{"level":"debug","ts":1681196258.0769823,"logger":"storage.mysql.sql","msg":"SELECT value FROM certmagic_data WHERE `key_hash` = d2834ecfc079ec0f668e7a99ff25bb42"}
{"level":"debug","ts":1681196258.078431,"logger":"tls","msg":"response from ask endpoint","domain":"maaiz.ptmn.com","url":"http://localhost:9999?domain=maaiz.ptmn.com","status":200}
{"level":"info","ts":1681196258.0784516,"logger":"tls.on_demand","msg":"obtaining new certificate","remote_ip":"172.105.36.145","remote_port":"60842","server_name":"maaiz.ptmn.com"}
{"level":"debug","ts":1681196258.0785513,"logger":"storage.mysql.sql","msg":"SELECT EXISTS(SELECT 1 FROM certmagic_data WHERE `key_hash` = dc16c872b8eef199641f76c16385949b)"}
{"level":"debug","ts":1681196258.079284,"logger":"storage.mysql.sql","msg":"SELECT EXISTS(SELECT 1 FROM certmagic_data WHERE `key_hash` = 1b0f72132cbd71ce53d6800d0fac696d)"}

3. Caddy version:

caddy-2.6.4

4. How I installed and ran Caddy:

a. System environment:

Caddy alpine docker image

b. Command:

command: ["caddy", "run", "--config", "/config/caddy/caddy.json"]

c. Service/unit/compose file:

Used kubernetes deployment

d. My complete Caddy config:

{
    "apps": {
        "http": {
            "http_port": 80,
            "servers": {
                "https": {
                    "listen": [":443"],
                    "listener_wrappers": [{ "wrapper": "go_proxyproto" }, { "wrapper": "tls" }],
                    "routes": [
                        {
                            "handle": [
                                {
                                    "handler": "reverse_proxy",
                                    "transport": { "protocol": "http", "tls": {} },
                                    "upstreams": [{ "dial": "localhost:9999" }]
                                }
                            ],
                            "terminal": true
                        }
                    ],
                    "tls_connection_policies": [{}]
                },
                "health-check": {
                    "listen": [":9999"],
                    "routes": [{ "handle": [{ "body": "OK", "handler": "static_response" }], "match": [{ "path": ["/"] }] }]
                }
            }
        },
        "tls": {
            "automation": {
                "on_demand": { "ask": "http://localhost:9999" },
                "policies": [
                    {
                        "issuers": [{ "module": "acme", "account_key": "<private_key_pem>" }],
                        "on_demand": true,
                        "key_type": "rsa4096",
                        "disable_ocsp_stapling": true
                    }
                ]
            }
        }
    },
    "logging": { "logs": { "default": { "level": "DEBUG", "writer": { "output": "stdout" } } } },
    "storage": { "module": "mysql", "dsn": "" }
}

5. Links to relevant resources:

Do you have the account set up as well, i.e. caddy/acme/acme-v02.api.letsencrypt.org-directory/users/default/default.json and such? Do you have the JSON files in place for the certs as well? The JSON files act like manifests.

Yes @francislavoie, I have the account setup, which is done by passing "account_key": "<private_key_pem>" in issuers.

In the in-house solution, we only have a private key, crt and chain for the certificates. So, for the certs I don’t have JSON files in place. How can the JSON files be generated? I see JSON file have a unique hash in issuer_data

{
	"sans": [
		"<fqdn>"
	],
	"issuer_data": {
		"url": "https://acme-v02.api.letsencrypt.org/acme/cert/03e251ba64b6e75f4b7f4114127a7534285e"
	}
}

PS: We do have private, and public CSR key pairs for each certificate though.

I’m not sure how that hash is obtained. You’d probably have to look at your issuance logs to match them up.(if you logs have that information).

You’d probably be better off just letting Caddy issue new certs for all domains.

@francislavoie: Yes that’s the best way, however, are there any good solutions to avoid hitting the LetsEncrypt rate limit as when I switch to Caddy there would be easily ~2k certificates request in 3hrs initially.

Caddy will issue certs as fast as possible, within its internal rate limit of 10 per 10 seconds (effectively 60 per minute). ZeroSSL doesn’t have rate limits, so it may switch to ZeroSSL if Let’s Encrypt rate limits are hit.

Make sure your config isn’t preventing issuer fallback though (having only "module": "acme" in your issuers config would prevent that).

Also since you’ve enabled on_demand, it won’t necessarily get certs for all domains right away, it will do it on the first request. If those domains have low activity, it might take longer for that to happen.

ZeroSSL appeared comparatively a lot slower to me. Also, we don’t want consumers to add an additional CAA record for ZeroSSL for now.

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