I am using Caddy as a reverse proxy engine, and making use of Caddy’s functionality to automatically generate and maintain certs. One of the services that Caddy is proxying to also needs a cert for its own functionality, so I need to re-use the generated cert. What is the standard/best practice way of accomplishing this? Since Caddy can potentially use one of multiple services to generate certs, the exact folder the cert is in may change at any given time. I imagine one could devise a rube goldberg script that takes this into account and figures out the newest available script, copies stuff around, etc. But wanted to check if there is a simple and supported method using the official Caddy binary.
I did come across this document, but for this usecase (certificate generated) it appears to depend on an unofficial plugin which isn’t thoroughly tested: “For example, to run a command after a certificate is obtained (third-party plugin required)”.
It is EXPERIMENTAL and subject to change. After getting some production experience, if demand is high enough and if this is generally useful to most users, we may move this into the standard Caddy distribution. It would be the only Caddy module that executes commands on the system, so we want to make sure it does not allow for arbitrary commands to be executed.
Which gave me pause. I figured there is probably a way people are doing this on production servers, if it’s not this way.
Experimental just means “the API/interfaces might change based on user feedback, so be aware you may need to react and adjust your config at some point”. It’s entirely usable as-is though.
One other reason it’s a plugin is because there’s a certain amount of danger to be able to run arbitrary commands defined in config if someone finds a way to change config at runtime (extremely unlikely, but it would allow escalating a vulnerability via arbitrary command execution, if there was one).
For reference, I am using Caddy to proxy HTTP/JMAP for a Stalwart instance while Stalwart listens directly on SMTP and IMAP. Stalwart needs a cert for IMAP and SMTPS, of course, so what I do is this:
Caddy orders the cert.
Caddy saves it to <caddyroot>/certificates/acme-v02.api.letsencrypt.org-directory/<domain>/<domain>.crt, caddyroot being /var/lib/caddy/ by default.
A Systemd path unit listens on changes to that certificate.
Upon change of the cert, it and the key are put to where Stalwart expects it to find.
Thanks for the context and suggestion! As a note, my understanding is that, while Caddy defaults to Lets Encrypt, it will fall back to other providers such as ZeroSSL if it fails to renew/generate a cert from LE. In such a case, the new cert would be placed in a different, relevantly named folder. The chances of this happening are probably low, but nonetheless present, which is what gave me pause on just checking a single folder. I’m guessing there might be other cases and reasons the exact folder name would change as well, whether due to in-prod scenarios, or version/API changes on Caddy’s end or one of the underlying tools, such as ACME. So a truly robust solution would need to do some fancy magic where it iterates through the whole “certificates” folder and deduces which cert is the newest/correct one.
I was ready to do this if needed, but since I already took the plunge into using the events exec module, I’ll be sticking with that for now. Just wanted to explain the reason I didn’t initially go with the solution you showed here, in case my reasoning is useful to you as well!
You concern is addressed by explicitly specifying Let’s Encrypt Live as ca. Also be advised that any ACME client will attempt renewal way ahead of the expiration date, so even small downtimes of Let’s Encrypt are not a problem.
If you insist on the fallback, though, my suggestion would be to create another set of path/service units and to use conditionals: ConditionPathExists or ConditionDirectoryNotEmpty, like so: