I saw Migrating to Caddy with existing LetsEncrypt certs which is already closed without a real solution for me. I would really like to re-use existing keys to not bother clients. is that possible? isnt it just a copy of the existing certs, keys, … into the right place? i did not find any documentation about how caddy stores certs, so i could not figure out where to copy them.
this seems to be the structure:
./
├── acme/
│ ├── acme-staging-v02.api.letsencrypt.org-directory/
│ │ ├── challenge_tokens/
│ │ └── users/
│ │ └── default/
│ │ ├── default.json
│ │ └── default.key
│ └── acme-v02.api.letsencrypt.org-directory/
│ ├── challenge_tokens/
│ └── users/
│ └── default/
│ ├── default.json
│ └── default.key
├── certificates/
│ └── acme-v02.api.letsencrypt.org-directory/
│ └── <domain>/
│ ├── <domain>.crt
│ ├── <domain>.json
│ └── <domain>.key
├── instance.uuid
├── last_clean.json
└── locks/
problem might be to create the correct .json files. i assume caddy cant produce those from existing files.
Correct, not without some help, since where it gets the certs from could be anything, so I don’t think we can simply infer this information:
{
"sans": [
"caddyserver.com"
],
"issuer_data": {
"url": "https://acme-v02.api.letsencrypt.org/acme/cert/0511c4442ef72e3dfc960207473148a06a47",
"ca": "https://acme-v02.api.letsencrypt.org/directory",
"account": "https://acme-v02.api.letsencrypt.org/acme/acct/12345",
"renewal_info": {
"suggestedWindow": {
"start": "2026-04-14T22:08:49Z",
"end": "2026-04-16T17:19:39Z"
},
"_uniqueIdentifier": "jw0TovYuftFDcDMYOF1YjiNykco.ARRERC73Lm38lgIHRzFIoGo2",
"_retryAfter": "2026-03-25T17:33:06.204182485Z",
"_selectedTime": "2026-04-15T15:34:24Z"
}
}
}
A custom script would probably have to fill this in.
It’s not all strictly required but I’m not sure what the behavioral guarantees would be with some information missing or incorrect.
We could explore “adopting” existing certs+keys without a metadata file, but this would probably require a Business-tier sponsorship or higher to sufficiently test and support.
Why would that matter to clients? Unless you’re developing somekind of app that specifically does key pinning, this won’t matter. All that’s important is that the private key is secret and proven to be under control of the entity controlling the domain, which is what ACME automation proves.
Cert issuance takes seconds at most, so you’re best off just spinning up Caddy and letting it issue a cert at startup and not worry about migrating anything.
i consider it a bad habit for a server to change its key with no good reason. a client should always do pinning in that regards and warn the user. but that may be a personal attitude most people wont follow…
Frequent key rotation is generally good. It limits the scope of potential compromises.
Key pinning is generally bad. It only works if keys don’t get rotated (which they should), and if the key is compromised, you are now pinned to a compromised key.
Clients pinning keys are asking for trouble.
I think key pinning was removed from most major browsers as it was a major foot gun for people that didn’t understand it properly, so it doesn’t even work any longer anyway. But back when it was in use the recommendation was to pin the root or intermediate certificates instead of the leaf to allow for key rotation.
so why does ssh do key pinning by default?
Different protocols, different purpose, different things.
SSH pins host keys because there is no central authority verifying server identity. The first time you connect, you trust that key, and then you detect any changes later.
HTTPS relies on public Certificate Authorities. The server proves its identity using a certificate signed by a trusted CA, so clients do not need to pin anything locally. This is also why browser certificate pinning was deprecated, since it caused more operational issues than security benefits.
With Let’s Encrypt, the standard and recommended approach is to issue a new certificate with a new key on the new server. Copying private keys between machines increases risk and goes against best practices.
TLDR; SSH key pinning and HTTPS certificates are fundamentally different mechanisms.
thank you for your patience to explain it to me. so the CA makes the difference. probably i dont like that much because i distrust many of those. but i like to idea of a fixed priviate-key to domain-name relation as ssh uses it.
thanks again and lets close it here.
The point of Let’s Encrypt is that it’s a non-profit entity, so they have no incentive to try to profit from it, they only have the public’s interest in mind, and that’s securing the web. So if anything, it’s the most trustworthy option, rather than using paid CAs that are incentivized to keep charging you for renewals etc.
dont get me wrong, i did not mean letsencrypt. but look into the list of by default trusted root certs. many do not inspire confidence.
You can choose to untrust some on your machines if you want, but those lists are maintained via pretty rigorous checks on the processes the CAs use to protect the keys etc
Reusing the same TLS private key for a long time actually increases risk.
If that key ever gets compromised, someone can impersonate your server until you rotate it. The longer you keep using the same key, the longer that window stays open. Rotating keys just keeps that risk smaller.
Copying the same private key between servers also doesn’t help, since now more systems have access to the same secret.
It’s generally better to let the new server generate its own key and get a fresh certificate.
Heartbleed is a good example of why this matters in practice.
You can always create your own CA and use that to issue certs for your servers. It’s actually pretty straightforward.
That’s probably the closest thing to SSH style trust in the HTTPS world, since you’re deciding what to trust instead of relying on the default root CAs.
You don’t have to trust them personally. Each root accepted into a trust store has to undergo rigorous technical and policy verifications and audits. Certificate transparency is also required by most, which ensures there’s no funny business in issuing certs.
The actual CA involved doesn’t matter anyway. You generate the key on your own local machine. Then you generate a certificate signing request to the CA. The CA signs the request and generates a certificate. At no point does the CA ever see your private key. It’s local to your own machine. At this point it is technically possible to generate a new certificate signing request using the same private key, in which case the signed certificate that’s returned by the CA uses the same key, but there’s very little point in doing so. (Not quite the same thing that Caddy does as Caddy uses ACME, but same principle).
Key pinning was the one and only point in doing this. For everything else it’s better to generate a new key on every request so that if that key is ever compromised it doesn’t really matter as it’s been replaced. No browser cares if the key has changed. It simply uses the new public key regardless. And as mentioned on the previous link no browser actually checks for key pinning in 2026 anyway.
These days we have Certificate Transparency logs which you can search at hundreds of places on the net. Search for “Certificate Transparency log search” and type your domain name in. If there was any certificates misissued they would be logged and likely banned from ever issuing certs again.