Install a TLS certificate on HAProxy
HAProxy expects a single PEM file containing the server certificate, its intermediates and the private key, concatenated in that order, referenced with `bind :443 ssl crt /path/site.pem`. It is the only common TLS server that wants the private key in the same file as the certificate, which is the detail people carry over incorrectly from nginx. Point `crt` at a directory instead of a file and HAProxy loads every certificate in it and selects by SNI.
Build the combined file #
cat certificate.crt chain.crt private.key > /etc/haproxy/certs/site.pem chmod 600 /etc/haproxy/certs/site.pem Configure #
global
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
frontend https
bind :80
bind :443 ssl crt /etc/haproxy/certs/site.pem alpn h2,http/1.1
http-request redirect scheme https unless { ssl_fc }
http-response set-header Strict-Transport-Security "max-age=63072000"
default_backend web
backend web
server app1 10.0.0.10:8080 check Replace these values
| Placeholder | What to put there |
|---|---|
crt /etc/haproxy/certs/site.pem | One file holding certificate, chain and key. |
alpn h2,http/1.1 | Advertises HTTP/2. Without it clients fall back to HTTP/1.1. |
ssl_fc | True when the connection arrived over TLS — used here for the redirect. |
Several certificates #
bind :443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 strict-sni Every file in the directory is loaded and selected by SNI. strict-sni rejects a connection whose
SNI matches nothing, rather than quietly serving whichever certificate sorted first — which is
otherwise a confusing way to serve the wrong site.
Validate before reloading #
haproxy -c -f /etc/haproxy/haproxy.cfg Expected output
Configuration file is valid
systemctl reload haproxy Verify from outside #
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| grep -c "BEGIN CERTIFICATE" Expected output
2
A count of 1 means chain.crt was left out of the concatenation.
Starting from a PFX #
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out /tmp/private.key
openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out /tmp/certificate.crt
openssl pkcs12 -in certificate.pfx -cacerts -nokeys -out /tmp/chain.crt
cat /tmp/certificate.crt /tmp/chain.crt /tmp/private.key > /etc/haproxy/certs/site.pem
chmod 600 /etc/haproxy/certs/site.pem Delete the temporary files afterwards — two of them contain the private key.
Mutual TLS #
bind :443 ssl crt /etc/haproxy/certs/site.pem \
ca-file /etc/haproxy/certs/client-ca.crt verify required
http-request set-header X-Client-DN %{+Q}[ssl_c_s_dn] Use verify optional while rolling out, so clients without a certificate still connect. See
mutual TLS.
Reloading a renewed certificate without dropping connections #
echo -e "set ssl cert /etc/haproxy/certs/site.pem <<\n$(cat /etc/haproxy/certs/site.pem)\n" \
| socat stdio /var/run/haproxy/admin.sock
echo "commit ssl cert /etc/haproxy/certs/site.pem" | socat stdio /var/run/haproxy/admin.sock Common HAProxy failures #
| Symptom | Cause |
|---|---|
unable to load SSL private key |
The key was not concatenated into the file |
| Clients report an untrusted issuer | chain.crt missing from the concatenation |
| Wrong certificate served | No SNI match; add strict-sni to make it explicit |
bind fails at startup |
Wrong path, or the file is not readable by HAProxy |
Frequently asked questions
- Why does HAProxy want the key in the same file?
- It is simply the interface it was designed with — one file per TLS identity, loaded whole. It makes a certificate directory easy to manage, at the cost of a file that must be protected like a private key, because it contains one.
- Does the order inside the file matter?
- The certificate must come before its intermediates. The key is conventionally last but HAProxy accepts it anywhere in the file. Keep to certificate, chain, key so the file is predictable to anyone reading it.
- How do I serve several certificates?
- Point
crtat a directory. HAProxy loads every file in it and selects by SNI. Addstrict-snito reject connections whose SNI matches nothing rather than falling back to the first certificate. - Can HAProxy reload certificates without a restart?
- Yes, through the runtime API —
set ssl certfollowed bycommit ssl certon the admin socket. That makes renewal possible with no dropped connections.
References
- HAProxy configuration manual — the bind ssl crt syntax and what the file must contain