Let's Encrypt Standalone ACME with HAProxy

HAProxy can be used to flexibly manage multiple Let's Encrypt certificates. This is useful when reverse proxying microservices without the need for a web server or exposing certbot publicly.

Example HAProxy Frontend

frontend default
bind 0.0.0.0:443 ssl crt /etc/ssl/haproxy/
bind 0.0.0.0:80
mode http

# Redirect non-ACME challenges to HTTPS
http-request redirect scheme https code 301 if !
{ ssl_fc } !is_acme_challenge
http-request set-header X-Forwarded-Proto "https"
http-request set-header X-Forwarded-Port "443"

# HSTS header
http-response set-header Strict-Transport-Security max-age=63072000

# ACME challenge ACL
acl is_acme_challenge path_beg /.well-known/acme-challenge/

# ACME challenge backend
use_backend certbot if is_acme_challenge

Example HAProxy Backend

backend certbot
mode http
server local 127.0.0.1:10081

HAProxy Deploy Hook

If the certificate is intended to be used by HAProxy itself, a deploy hook can be used with certbot to create a compatible PEM file.

# /opt/certbot/example.tld.sh
cat /etc/letsencrypt/live/example.tld/fullchain.pem /etc/letsencrypt/live/example.tld/privkey.pem | tee /etc/ssl/haproxy/example.tld.pem

Certbot Example

Using standalone mode, the following will listen on 127.0.0.1:10081 for HTTP challenges. HTTP requests to /.well-known/acme-challenge/ will be routed to the certbot backend by HAProxy.

#!/bin/sh
certbot -a standalone \
--cert-name 'example.tld' -d 'example.tld' \
--deploy-hook "/opt/certbot/example.tld.sh" \
--http-01-address 127.0.0.1 --http-01-port 10081 --preferred-challenges http-01 \
--keep-until-expiring --text --agree-tos --non-interactive certonly \
--rsa-key-size 4096 \
&& (systemctl restart haproxy)