This is the sixth in a series of several posts on how to do way more than you really need to with Let’s Encrypt, certbot
, and a good server. I use all of these things regularly but I’ve never taken the time to take them apart, look at how they work, and spend hours in Google trying in vain to figure out how to put them back together. It was inspired by a disturbing trend of ISP privacy violations and the shocking regulatory capture of the US Federal Communications Commission.
This post looks at several different ways to automate cert renewal. I tried to cater to everyone by including cron
and systemd
options. If you don’t have your server set up to send emails, you might want to do that first.
- The Series so Far
- Code
- Note
- Automating Renewals
- Hooks
- Scripting a Renewal
- Scheduling the Renewal
- Before You Go
- Legal Stuff
The Series so Far
- Overview
- First Steps
- Tuning with OpenSSL
- Useful Headers
- Generating and Testing a Cert
- Automating Renewals
Things that are still planned but probably not soon:
- Updating OpenSSL
- CSP Playground
- Vagrant Examples (don’t hold your breath)
Code
You can view the code related to this post under the post-06-automation
tag. If you’re curious, you can also check out my first draft.
Note
I’m testing out some new tooling. This will be wotw-highlighter
’s shakedown run. Let me know what you think about the syntax highlighting! I’m pretty excited because (no whammies) it should work well in AMP and normally.
I wrote the majority of the Apache examples with httpd
in mind, i.e. from a RHEL perspective. If you instead use apache2
, most of the stuff should still work, albeit maybe just a little bit different.
Automating Renewals
Now that everything’s installed and in place, we’ve got to think about keeping the cert current. Let’s Encrypt certs have a 90-day lifetime, which is substantially shorter than a typical commercial cert. certbot
is built to handle automated renewals and can update everything in place without any intervention on your part.
If you try running certbot renew
right now, you’ll probably get something like this:
$ sudo certbot renew |
While the cert isn’t due for renewal, we can actually test the renewal process like this:
$ certbot renew --dry-run |
This is useful to make sure everything’s in place for automation.
Hooks
You might have noticed the Dry run: skipping deploy hook command
line in the output. certbot
can run commands or scripts at several stages in its lifecycle. You can either add hooks via flags every time you renew
, or you can offload them to executable scripts in /etc/letsencrypt/renewal-hooks
.
For this example, all I’d like to do is restart the server process following successful renewals. Assuming we’ve got a local script capable of that called server-reboot
, this should add it to certbot
’s pipeline.
$ sudo cp ./server-reboot /var/letsencrypt/renewal-hooks/deploy |
Nginx
/var/letsencrypt/renewal-hooks/deploy/server-reboot | |
1 2 3 | #!/bin/bash |
Apache
/var/letsencrypt/renewal-hooks/deploy/server-reboot | |
1 2 3 | #!/bin/bash |
Scripting a Renewal
The official documentation suggests running an automated renewal task at least twice a day (e.g. the CentOS instructions; scroll down). certbot
also asks that you run it at a random minute. To make things easier later, let’s isolate our renew command:
/sbin/certbot-renew-everything | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/bin/bash |
$ sudo chmod 'u=rwx,go=' /sbin/certbot-renew-everything |
Adding extra flags is straightforward. renew
only exits with a nonzero code if the renewal failed (paragraph right above the link), meaning the skipped renewals we saw earlier don’t generate any traffic. They do, however, send many things to STDOUT
, which is enough to trigger cron
’s mail action. The quiet
flag suppresses STDOUT
, so you won’t get multiple emails a day letting you know certbot
did nothing. If you’re into that you don’t have to use it.
Most of the solutions I’ve seen for the randomness do some cool stuff with advanced PRNG and then pass the result to sleep
. There’s nothing wrong with sleep
if you’re pausing tasks that don’t actually need to run. Anything that kills the thread kills the task.
at
at
provides a much better solution because, via man at
,
The
at
utility shall read commands from standard input and group them together as anat-job
, to be executed at a later time.
In other words, at
is a single-execution cron
. It manages an at
queue, most likely accessible via atq
, which means random power failure or accidentally nuking a remote session won’t kill the delayed task. Of course that means some setup is required:
$ sudo yum install -y at |
at
is the command itselfatd
is theat
daemonatq
is an alias for listingat
jobsatrm
is an alias for removingat
jobs
Block Scheduling
The simplest at
solution triggers a script like this NUMBER_OF_DAILY_RUNS
times per day.
/sbin/certbot-renew-everything | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/bin/bash |
Random Scheduling
A slightly more involved at
script calls both the task and itself.
Update 2019/05: Mischa Fiala (@mikfiala) pointed out my original script didn’t work. This updated version should.
/sbin/at-random-renewal | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | #!/bin/bash |
Scheduling the Renewal
With or without at
, you’ve got to ensure the task is actually being run.
cron
$ sudo crontab -e |
crontab -e | |
1 2 3 4 5 6 7 8 | # Add a `MAILTO` that looks like this: |
If you’re not changing the time in the script itself, you probably don’t want to use 0 0,12
. This launches the task at 00:00
and 12:00
every day. If launching means at
assigns a random time, or checks to see if it’s running, those times aren’t a problem. If you’re actually hitting Let’s Encrypt every day at that time, that’s not a great idea.
systemd
(Note: my systemd
knowledge is still pretty rudimentary. I’m using to userspace cron
. If you see anything I can improve, I’d love to know about it!)
We’re going to define a oneshot unit (example #2):
/etc/systemd/system/certbot-renew.service | |
1 2 3 4 5 6 7 8 | [Unit] |
$ sudo chmod 'ugo=r,u+w' /etc/systemd/system/certbot-renew.service |
To run it regularly, we also create a timer:
/etc/systemd/system/certbot-renew.timer | |
1 2 3 4 5 6 | [Unit] |
$ sudo chmod 'ugo=r,u+w' /etc/systemd/system/certbot-renew.service |
Before You Go
Let’s Encrypt is a fantastic service. If you like what they do, i.e. appreciate how accessible they’ve made secure web traffic, please donate. EFF’s certbot
is what powers my site (and basically anything I work on these days); consider buying them a beer (it’s really just a donate link but you catch my drift).
Legal Stuff
I’m still pretty new to the whole CYA legal thing. I really like everything I’ve covered here, and I’ve done my best to respect individual legal policies. If I screwed something up, please send me an email ASAP so I can fix it.
- The Electronic Frontier Foundation and
certbot
are covered by EFF’s generous copyright. As far as I know, it’s all under CC BY 3.0 US. I made a few minor tweaks to build the banner image but tried to respect the trademark. I don’t know who thecertbot
logo artist is but I really wish I did because it’s a fantastic piece of art. - Let’s Encrypt is trademarked. Its logo uses CC BY-NC 4.0. I made a few minor tweaks to build the banner image but tried to respect the trademark.
- I didn’t find anything definitive (other than EULAs) covering Nginx, which doesn’t mean it doesn’t exist. Assets were taken from its press page.
- Apache content was sourced from its press page. It provides a full trademark policy.