-
Notifications
You must be signed in to change notification settings - Fork 5
Letsencrypt integration with HAProxy and acme.sh
acme.sh integrates smoothly with HAProxy, one is able to start an haproxy configuration without any certificate, generate certificates with acme.sh and populate HAProxy with them. This will run acme.sh as non-root.
This tutorial uses a script which was not merged into acme.sh, if you have any issues, please report on the Pull Request https://github.com/acmesh-official/acme.sh/pull/4581
This procedure was written for ubuntu 22.04, but you can easily adapt it for other OSes.
Please install the packages available on http://haproxy.debian.net, this way you will have an up to date version of HAProxy with the latest fixes.
root@ubuntu:~# sudo apt-get install --no-install-recommends software-properties-common
root@ubuntu:~# sudo add-apt-repository ppa:vbernat/haproxy-2.6
root@ubuntu:~# sudo apt-get install haproxy=2.6.\*
root@ubuntu:~# sudo apt-get install socat
Unfortunately acme.sh is not available as a package but its installation is easy. In this tutorial we will chose to run acme.sh with its own user which will have rights in the haproxy group in order to push certificates at the right place.
Create the acme user:
root@ubuntu:~# adduser --system --disabled-password --disabled-login --home /var/lib/acme --quiet --force-badname --group acme
root@ubuntu:~# adduser acme haproxy
root@ubuntu:~# mkdir /usr/local/share/acme.sh/
Install acme.sh in /usr/local/:
root@ubuntu:~# git clone https://github.com/acmesh-official/acme.sh.git
root@ubuntu:~# cd ./acme.sh
root@ubuntu:~# ./acme.sh --install --no-cron --no-profile --home /usr/local/share/acme.sh
root@ubuntu:~# ln -s /usr/local/share/acme.sh/acme.sh /usr/local/bin/
root@ubuntu:~# chmod 755 /usr/local/share/acme.sh/
Generate your acme account from the acme user
root@ubuntu:~# sudo -u acme -s
acme@ubuntu:~$ acme.sh --register-account
[Mon Apr 24 01:28:14 PM UTC 2023] Create account key ok.
[Mon Apr 24 01:28:14 PM UTC 2023] Registering account: https://ACMESERVER/acme/acme/directory
[Mon Apr 24 01:28:14 PM UTC 2023] Registered
[Mon Apr 24 01:28:14 PM UTC 2023] ACCOUNT_THUMBPRINT='lCufto4sDRTHdmWL0EugFywGV54hBCuTTXvwifi65R4'
Write the thumbprint somewhere, it will be used to configure HAProxy.
acme.sh is able to run in stateless mode, which means it will only be used as an ACME client, but the HTTP challenge will be delivered by a third-party, in our case this will be HAProxy. In order to achieve this, HAProxy needs to return a specific value.
Create the directory for certificates:
root@ubuntu:~# mkdir /etc/haproxy/certs
root@ubuntu:~# chown haproxy:haproxy /etc/haproxy/certs
root@ubuntu:~# chmod 770 /etc/haproxy/certs
Then edit /etc/haproxy/haproxy.cfg to add the challenge response and the thumbprint. Don't forget the stats socket with correct permissions which are needed for the deployment script:
global
stats socket /var/run/haproxy/admin.sock level admin mode 660
setenv ACCOUNT_THUMBPRINT 'lCufto4sDRTHdmWL0EugFywGV54hBCuTTXvwifi65R4'
frontend web
bind :80
bind :443 ssl crt /etc/haproxy/certs/ strict-sni
http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
You can then start HAProxy with the empty directory, the strict-sni keyword allows it.
At this step you can generate a certificate with acme.sh, and haproxy will respond to the challenge, you should be able to see it in your haproxy logs. Run the command as the acme user:
root@ubuntu:~# sudo -u acme -s
acme@ubuntu2204:~$ acme.sh --issue -d domain1.com --stateless
[Mon Apr 24 01:36:03 PM UTC 2023] Using CA: https://ACMESERVER/acme/acme/directory
[Mon Apr 24 01:36:03 PM UTC 2023] Single domain='domain1.com'
[Mon Apr 24 01:36:03 PM UTC 2023] Getting domain auth token for each domain
[Mon Apr 24 01:36:04 PM UTC 2023] Getting webroot for domain='domain1.com'
[Mon Apr 24 01:36:04 PM UTC 2023] Verifying: domain1.com
[Mon Apr 24 01:36:04 PM UTC 2023] Stateless mode for domain:domain1.com
[Mon Apr 24 01:36:06 PM UTC 2023] Success
[Mon Apr 24 01:36:06 PM UTC 2023] Verify finished, start to sign.
[Mon Apr 24 01:36:06 PM UTC 2023] Lets finalize the order.
[Mon Apr 24 01:36:06 PM UTC 2023] Le_OrderFinalize='https://ACMESERVER/acme/acme/order/VTf79HKx9LeAqKIn3JVLfAQHE20G5o72/finalize'
[Mon Apr 24 01:36:06 PM UTC 2023] Downloading cert.
[Mon Apr 24 01:36:06 PM UTC 2023] Le_LinkCert='https://ACMESERVER/acme/acme/certificate/2neCjpZPEWa1wkbxOQjcFo3R0Xm1XJeX'
[Mon Apr 24 01:36:06 PM UTC 2023] Cert success.
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
[Mon Apr 24 01:36:06 PM UTC 2023] Your cert is in: /var/lib/acme/.acme.sh/domain1.com_ecc/domain1.cer
[Mon Apr 24 01:36:06 PM UTC 2023] Your cert key is in: /var/lib/acme/.acme.sh/domain1.com_ecc/domain1.key
[Mon Apr 24 01:36:06 PM UTC 2023] The intermediate CA cert is in: /var/lib/acme/.acme.sh/domain1.com_ecc/ca.cer
[Mon Apr 24 01:36:06 PM UTC 2023] And the full chain certs is there: /var/lib/acme/.acme.sh/domain1.com_ecc/fullchain.cer
Once the certificate is generated you can deploy it in the certs directory and to haproxy without reload it, with the stats socket.
You must set the DEPLOY_HAPROXY_STATS_SOCKET
and DEPLOY_HAPROXY_PEM_PATH
with the rights value corresponding to the path of the haproxy socket and the directory which will store the certificates. It is also possible to use the 'DEPLOY_HAPROXY_MASTER_CLI' to use the master CLI instead of a stats socket. Be careful not to add any trailing slash in DEPLOY_HAPROXY_PEM_PATH
as it won't be able to match the haproxy configuration.
root@ubuntu:~# sudo -u acme -s
acme@ubuntu:~$ DEPLOY_HAPROXY_HOT_UPDATE=yes DEPLOY_HAPROXY_STATS_SOCKET=UNIX:/var/run/haproxy/admin.sock DEPLOY_HAPROXY_PEM_PATH=/etc/haproxy/certs acme.sh --deploy -d domain1.com --deploy-hook haproxy
[Mon Apr 24 02:17:31 PM UTC 2023] The domain 'domain1.com' seems to have a ECC cert already, lets use ecc cert.
[Mon Apr 24 02:17:31 PM UTC 2023] Deploying PEM file
[Mon Apr 24 02:17:31 PM UTC 2023] Moving new certificate into place
[Mon Apr 24 02:17:31 PM UTC 2023] Creating new certificate '/etc/haproxy/certs/domain1.com.pem' over HAProxy stats socket.
[Mon Apr 24 02:17:31 PM UTC 2023] Success
This command will deploy the certificate to /etc/haproxy/certs and inject the certificate over haproxy stats socket.
Once it succeed you can check if the certificate was added in HAProxy with socat:
root@ubuntu:~# echo "show ssl cert /etc/haproxy/certs/domain1.com.pem" | socat /var/run/haproxy/admin.sock -
Filename: /etc/haproxy/certs/domain1.com.pem
Status: Used
Serial: 6C058BA29A03DF58BFA9CF0ABD0C0AB8
notBefore: Apr 24 13:35:04 2023 GMT
notAfter: Apr 25 13:36:04 2023 GMT
Subject Alternative Name: DNS:domain1.com
Algorithm: EC256
SHA1 FingerPrint: 7A6A64B2D3BB1E548DBF383FDB3A7CA8434B3F5A
[...]
And then directly with curl:
$ curl https://domain1.com
The certificates can be renewed and updated automatically using the --cron
option.
It is recommended to use the crontab of the acme
user or a systemd timer.
Inspired from https://github.com/acmesh-official/acme.sh/wiki/Using-systemd-units-instead-of-cron
Create a new systemd service in /etc/systemd/system/acme_letsencrypt.service
:
[Unit]
Description=Renew Let's Encrypt certificates using acme.sh
After=network-online.target
[Service]
Type=oneshot
# --home's argument should be where the acme.sh script resides.
ExecStart=/usr/local/bin/acme.sh --cron
User=acme
Group=acme
SuccessExitStatus=0 2
Test the service:
root@ubuntu:~# systemctl daemon-reload
root@ubuntu:~# systemctl start acme_letsencrypt
Create a time in /etc/systemd/system/acme_letsencrypt.timer
[Unit]
Description=Daily renewal of Let's Encrypt's certificates
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer:
root@ubuntu:~# systemctl start acme_letsencrypt.timer
root@ubuntu:~# systemctl enable acme_letsencrypt.timer
Using the crontab is easy since this is integrated in acme.sh:
root@ubuntu:~# sudo -u acme -s
acme@ubuntu:~$ acme.sh --install-cronjob