I struggled quite a bit trying to figure out how to use the new directive to dynamically update certificates with HAProxy 2.1 when loading certificates from a directory. I think i got it right now, hope it is helpful to someone (and happy for feedback). The key point i missed for quite a while was that the certificate name for “set ssl cert” is the full path to the file and not just the filename.
script to update certificates from letsencrypt certbot:
#!/bin/bash
set -e
LE_DIR=/etc/letsencrypt/live
HA_DIR=/etc/ssl/private
DOMAINS=$(ls ${LE_DIR})
# update certs for HA Proxy
for DOMAIN in ${DOMAINS}
do
# also update the file in the filesystem for when haproxy restarts
cat ${LE_DIR}/${DOMAIN}/fullchain.pem ${LE_DIR}/${DOMAIN}/privkey.pem | tee ${HA_DIR}/${DOMAIN}.pem
echo -e "set ssl cert /etc/ssl/private/${DOMAIN}.pem <<\n$(cat ${HA_DIR}/${DOMAIN}.pem)\n" | socat stdio /var/run/haproxy
echo -e "commit ssl cert /etc/ssl/private/${DOMAIN}.pem" | socat stdio /var/run/haproxy
done
Note: This script does not work when you dynamically add new domains, as those new domains will not be known to HAProxy. From HAProxy 2.2 on, there seem to be additional commands to cover that use case, but afaik you would need to know which domain is new vs which is already existing, to run the correct thing.
Thanks @dbu. I’ve been using this strategy with certbot for a while now, but it stopped working recently. The issue, it turns out, is twofold.
The “chain.pem” file–which is included for convenience in “fullchain.pem”–issued by Let’s Encrypt now has an empty line. That blank line will need to be stripped out; otherwise, HAProxy will try to interpret part of the payload as a command and spit out “Unknown command…” errors.
DOMAINS=$(ls ${LE_DIR}) doesn’t quite work anymore since /etc/letsencrypt/live/ has a README file in it.
Lastly, I’d be hesitant to use tee since that may cause the private key to show up in logs. It may be more secure to use simple redirection (>).
I use the following, where “$CERT_NAME” holds the name of the certificate (e.g. $DOMAIN in the original script), as issued to certbot's --cert-name option.
LE_LIVE_DIR="/etc/letsencrypt/live/${CERT_NAME}"
LE_FULL_CERT_DIR=/etc/letsencrypt/full
FULL_CERT="${LE_FULL_CERT_DIR}/${CERT_NAME}.pem"
mkdir -p "${LE_FULL_CERT_DIR}"
# Create a single-file certificate with both the full CA chain and the
# private key. Empty lines are removed.
cat ${LE_LIVE_DIR}/fullchain.pem ${LE_LIVE_DIR}/privkey.pem | sed '/^$/d' > ${FULL_CERT}
Note the sed part, which removes the empty lines from fullchain.pem. Also note that I place the concatenated certificate in a new directory. Permissions and ownership should be set on the directory and file (e.g. owned by root with 600 permissions on the file).
I ran in the same “Unknow Command” problem.
I helped me with a “on-the-fly” removement of empty lines via grep .
I use the “acme.sh” feature “reloadcmd” to run a script which cat fullchain+key to the haproxy cert folder and then I use this bash loop
for line in $(echo "show ssl cert" | socat unix-connect:/run/haproxy/admin.sock -)
do
test -f "$line" || continue
echo -e "set ssl cert $line <<\n$(cat $line | grep . )\n" | socat unix-connect:/run/haproxy/admin.sock -
echo -e "commit ssl cert $line" | socat unix-connect:/run/haproxy/admin.sock -
done
Notice the piped grep . and remember, that I already renewed the files on the disk. So I can re-use the paths from “show ssl cert”.
And yes, in my case a simple “systemctl reload haproxy” would be easier and would not reset any connection in 99.999999% but its cool to have a cert-reload without reloading haproxy itself.
Edit: and yes, I know this discussion is a few months old, but as the commands of the official blog doesn’t work anymore (Dynamic SSL Certificate Storage in HAProxy - HAProxy Technologies), any additional information may help others, as its make it easier get a helpful google-hit.
Also, you win a UUOC award with cat $line | grep .. (UUOCs are fun, I can remember the award from way, way back.). You can replace it with grep . $line