Let's Encrypt hooks use cases
Hooks offer great flexibility to support many use cases. Learn how they can help you
Motivation
Hooks are the main elements of adapting certbot to your particular use case. I found a few application setups that depending on whether you can support authorizations while your app is running or not, there are different kind of hooks you should use.
Learn them and when to use them in this post.
Hooks overview
There are 3 hooks you should know about, and by using them you'd be able to cover most of the use cases.
The pre-hook runs before authorization. That means every time certbot tries to get a new certificate.
The post-hook runs after authorization. You can use these two to setup and tear down the environment required for getting a certificate.
And finally, deploy-hook runs when there is a new certificate.
The flow looks like this:
- pre-hook
- Authorization
- post-hook
- deploy-hook, if there is a new certificate
In effect, the pre-hook and the post-hook does not run when the authorization is not required. This is the case when the certificate is not near expiry and certbot decides it's not time to renew it. If you run the renew script every day, then the hooks will only run every other month.
The deploy-hook does not run if there is no new certificate. For example, if you use
certbot certonly --keep-until-expiring
, then you shouldn't rely on this hook to start your app. In that case,
use certbot ... && ...
.
certbot renew
gets the same hooks set for certonly
. If all the hooks are the same, that makes a far easier
configuration for renewal, as you don't need to input them again. If you don't want a particular hook to run for renew,
overwrite it like this: ---post-hook=""
. This can come handy when the renewal flow is different than getting an initial
certificate.
Case #1: The app runs continuously
In this case, the app starts before certbot runs, and also running during the renewal process. Use the deploy-hook to restart it after getting a new certificate, but otherwise it is a straightforward scenario.
As there is no difference between certonly
and renew
, you don't need to supply any arguments to the latter:
certbot certonly ... --deploy-hook="restart app..." ...
certbot renew
Keep in mind that you need a certificate even before certbot runs. You can use a self-signed one, but this is something you need to handle.
A note on auto-restarting the app
In some cases, you don't want to restart the app randomly when there is a new certificate. But as the deploy-hook can be anything, it can send an email so that you can schedule a maintenance restart.
Case #2: Shut down the app during renewal
In this case, the app isn't running during the authorization process, making port 80 available, and thus the standalone auth possible.
Starting the app works like this:
- Certbot gets a certificate
- Start the app
And the renewal:
- Shut down the app
- Certbot updates the cert
- Start the app
For the former, you don't need any hooks, just start the app after it is run:
certbot certonly --standalone --keep-until-expiring ... && start app...
By the time the app is running, the certificate will be there.
Two important parts to note here. The first is the --keep-until-expiring
, as certbot would prompt for what to do
if a certificate exists. This makes sure that if there is a valid cert, that would be used.
The other is that there is no deploy-hook here, but the ``&& ...``` construct. This is essential, as the hook would not run if there are no new certificates.
For the renewal, use hooks to stop/start the app:
certbot renew --pre-hook="stop app..." --post-hook="start app..." ...
Case #3: Get an initial cert then use app-based auth for renewal
This is a combination of the previous two cases. It gets a certificate before starting the app (that is, with the standalone auth), and then manages to renew the certificate with the app running.
In this case, the app does not need to handle an initial no-certificate scenario, as by the time it is started a valid one is already in place. In effect, you don't need a self-signed one.
The startup flow:
- Get a cert
- Start the app
The renewal flow:
- Get a cert
- Restart the app
Getting an initial cert can be done using this script:
certbot certonly --standalone --keep-until-expiring ... && start app...
And the renewal could work like this:
certbot renew --webroot ... --deploy-hook="restart app..."
For renewal, you can use any auth flow your app supports, but webroot
is the most likely.
Why you shouldn't use it
In this case, while the downtime can be smaller during renewals, the certonly
and the renew
flows are completely
different. This makes errors for the latter a lot less apparent, and it's likely it will take 2 months to see that something
is broken.
I believe it's better to use the same auth for both flows, making the configuration easier to understand.
Which one to use?
For most apps, case #2 is the way to go. The only downside is the short downtime every now and then, but unless you are working in some extremely high-SLA industry, it is just fine. Also, if the app uses some kind of auto-retrying, the users might not even notice it.
Also, if you want, you can ditch the renew altogether and schedule a maintenance restart every 60 days. The startup script takes care of renewals too if a certificate is nearing expiry. The only thing to keep in mind that Let's Encrypt might shorten the lifespan of its certs, so it's better not to hardcode it.