diff options
Diffstat (limited to 'posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html')
-rw-r--r-- | posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html b/posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html new file mode 100644 index 0000000..634530b --- /dev/null +++ b/posts/2021-03-19-how-to-automate-certbot-renewal-with-haproxy.html @@ -0,0 +1,256 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" href="/includes/stylesheet.css" /> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta + property="og:description" + content="The World Wide Web pages of Adam Carpenter" + /> + <meta + property="og:image" + content="https://nextcloud.53hor.net/index.php/s/Nx9e7iHbw4t99wo/preview" + /> + <meta property="og:site_name" content="53hor.net" /> + <meta + property="og:title" + content="How to Automate Certbot Renewal with HAProxy" + /> + <meta property="og:type" content="website" /> + <meta property="og:url" content="https://www.53hor.net" /> + <title>53hornet ➙ How to Automate Certbot Renewal with HAProxy</title> + </head> + + <body> + <nav> + <ul> + <li> + <a href="/"> + <img src="/includes/icons/home-roof.svg" /> + Home + </a> + </li> + <li> + <a href="/info.html"> + <img src="/includes/icons/information-variant.svg" /> + Info + </a> + </li> + <li> + <a href="https://git.53hor.net"> + <img src="/includes/icons/git.svg" /> + Repos + </a> + </li> + <li> + <a href="/hosted.html"> + <img src="/includes/icons/desktop-tower.svg" /> + Hosted + </a> + </li> + <li> + <a type="application/rss+xml" href="/rss.xml"> + <img src="/includes/icons/rss.svg" /> + RSS + </a> + </li> + </ul> + </nav> + + <article> + <h1>How to Automate Certbot Renewal with HAProxy</h1> + + <p> + So this is specifically for HAProxy on FreeBSD, but it should apply to + other *nix systems as well. Basically, I use HAProxy as a reverse proxy + to a bunch of servers I administer. I use Let's Encrypt for a + certificate and I used <code>certbot</code> to generate that + certificate. Generating the certificate for the first time is easy and + has lots of documentation, but it wasn't initially clear on how I could + easily set up auto-renewal. Here's how I did it. + </p> + + <p> + If you've already set up TLS termination with HAProxy and + <code>certbot</code>, you know you need to combine your Let's Encrypt + fullchain and private key to get a combined certificate that HAProxy can + use. You can do this by <code>cat</code>-ing the chain and key together + like so: + </p> + + <pre> +<code> +cat /usr/local/etc/letsencrypt/live/$SITE/fullchain.pem /usr/local/etc/letsencrypt/live/$SITE/privkey.pem > /usr/local/etc/ssl/haproxy.pem +</code> + </pre> + + <p> + In this example, <code>$SITE</code> is your domain name that you fed + HAProxy when you created the certificate and <code>haproxy.pem</code> is + wherever you're storing HAProxy's combined certificate. Your HAProxy + config then points to that certificate like this: + </p> + + <pre> +<code> +macon% grep crt /usr/local/etc/haproxy.conf + bind *:443 ssl crt /usr/local/etc/ssl/haproxy.pem +</code> + </pre> + + <p> + And that was the end of the first-time setup. Then a few months later + you probably had to do it again because Let's Encrypt certs are only + good for 90 days in between renewals. To renew the certificate, you + usually run <code>certbot renew</code>, it detects which certificates + are present, and uses either the webroot or standlone server renewal + process. Then you have to <code>cat</code> the fullchain and privkey + together and restart HAProxy so it starts using the new certificate. + </p> + + <p> + To automate those steps, newer versions of + <code>certbot</code> will run any post renewal hooks (read: scripts) + that you want. You can also configure HAProxy and + <code>certbot</code> to perform the ACME challenge dance for renewal so + that you don't have to use it interactively. + </p> + + <p> + First, if you haven't already done it, change your HAProxy config so + there's a frontend+backend for responding to ACME challenges. In a + frontend listening for requests, create an access control list for any + request looking for <code>/.well-known/acme-challenge/</code>. Send + those requests to a backend server with an unused local port. + </p> + + <pre> +<code> +frontend http-in + acl letsencrypt-acl path_beg /.well-known/acme-challenge/ + use_backend letsencrypt-backend if letsencrypt-acl + ... +backend letsencrypt-backend + server letsencrypt 127.0.0.1:54321 +</code> + </pre> + + <p> + What this will do is allow <code>certbot</code> and Let's Encrypt to + renew your server in standalone mode via your reverse proxy. As an added + bonus it prevents you from having to open up an additional port on your + firewall. + </p> + + <p> + Now you've gotta configure <code>certbot</code> to do just that. A + config file was created in <code>certbot</code>'s + <code>renew</code> directory for your site. All you need to do in that + file is add a line to the <code>[renewalparams]</code> section + specifying the port you're using in your HAProxy config. + </p> + + <pre> +<code> +macon% echo "http01_port = 54321" >> /usr/local/etc/letsencrypt/renewal/$SITE.conf +</code> + </pre> + + <p> + Now you need the post-renewal hooks. I dropped two separate scripts into + the <code>renewal-hooks</code> directory: one does the job of combining + the certificate chain and private key and the other just restarts + HAProxy. + </p> + + <pre> +<code> +macon% cat /usr/local/etc/letsencrypt/renewal-hooks/post/001-catcerts.sh +#!/bin/sh + +SITE=(your site of course) + +cd /usr/local/etc/letsencrypt/live/$SITE +cat fullchain.pem privkey.pem > /usr/local/etc/ssl/haproxy.pem +macon% cat /usr/local/etc/letsencrypt/renewal-hooks/post/002-haproxy.sh +#!/bin/sh +service haproxy restart +</code> + </pre> + + <p> + When <code>certbot renew</code> is run, <code>certbot</code> checks the + <code>renewal-hooks/post</code> directory and runs any executable things + in it after it's renewed the certificate(s). As a side note, + <em>make sure you hit those scripts with <code>chmod +x</code></em> or + they probably won't run. + </p> + + <p> + Now all that's left is dropping a job into <code>cron</code> or + <code>periodic</code> to run <code>certbot renew</code> at least once or + twice within the renewal period. + </p> + + <pre> +<code> +macon% doas crontab -l|grep certbot +# certbot renewal +@monthly certbot renew +</code> + </pre> + + <p> + You can always test that your scripts are working with + <code>certbot renew --dry-run</code> just to be safe. + </p> + + <pre> +<code> +macon% doas certbot renew --dry-run +Saving debug log to /var/log/letsencrypt/letsencrypt.log + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Processing /usr/local/etc/letsencrypt/renewal/53hor.net.conf +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Cert not due for renewal, but simulating renewal for dry run +Plugins selected: Authenticator standalone, Installer None +Simulating renewal of an existing certificate for 53hor.net and 7 more domains +Performing the following challenges: +http-01 challenge for 53hor.net +http-01 challenge for carpentertutoring.com +http-01 challenge for git.53hor.net +http-01 challenge for nextcloud.53hor.net +http-01 challenge for pkg.53hor.net +http-01 challenge for plex.53hor.net +http-01 challenge for theglassyladies.com +http-01 challenge for www.53hor.net +Waiting for verification... +Cleaning up challenges + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +new certificate deployed without reload, fullchain is +/usr/local/etc/letsencrypt/live/53hor.net/fullchain.pem +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Congratulations, all simulated renewals succeeded: + /usr/local/etc/letsencrypt/live/53hor.net/fullchain.pem (success) +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Running post-hook command: /usr/local/etc/letsencrypt/renewal-hooks/post/001-catcerts.sh +Running post-hook command: /usr/local/etc/letsencrypt/renewal-hooks/post/002-haproxy.sh +Output from post-hook command 002-haproxy.sh: +Waiting for PIDS: 15191. +Starting haproxy. + +</code> + </pre> + + <p> + And there it is. Automated Let's Encrypt certificate renewal on FreeBSD + with HAProxy. + </p> + </article> + </body> +</html> |