Skip to content

maxuru ~ % cat haproxy/tls-configuration

Configure TLS on HAProxy

Severity: highApplies to: HAProxy 2.xApplies to: HAProxy 3.xApplies to: OpenSSL 1.1.1+
The fix/etc/haproxy/haproxy.cfg
global
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
frontend web
bind :443 ssl crt /etc/haproxy/certs/site.pem alpn h2,http/1.1

The ssl-default-* settings apply to every bind line that does not override them, which is what makes this one change rather than one per listener.

Older configs disable protocol versions one at a time:

# Fragile — each new obsolete version needs another line
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11

That works but describes the past. ssl-min-ver states the floor directly:

ssl-default-bind-options ssl-min-ver TLSv1.2

Anything below is refused, including versions nobody has thought about yet. The two forms can be combined, and when they disagree the result is confusing — prefer the floor alone.

TLS 1.2 is the right floor for a general-purpose site in 2026. TLS 1.0 and 1.1 are deprecated by RFC 8996 and rejected by current browsers. A 1.3-only floor is defensible for internal services and will cut off older API clients.

Two cipher settings, and one of them does not do what its name suggests

Section titled “Two cipher settings, and one of them does not do what its name suggests”

This trips people up, and it is the same trap as nginx’s ssl_ciphers:

  • ssl-default-bind-ciphers controls TLS 1.2 and below only.
  • ssl-default-bind-ciphersuites controls TLS 1.3 (OpenSSL 1.1.1+).

A config that carefully curates ssl-default-bind-ciphers and stops there has said nothing about TLS 1.3 — which is most of the traffic. The 1.3 suites are few and all sound, so leaving ciphersuites at its default is fine; the mistake is believing the first setting covered it.

Both lists come from the Mozilla SSL Configuration Generator, which is worth generating fresh rather than copying from a blog post — cipher recommendations move.

If your certificates come from Let’s Encrypt, remove these:

# No longer does anything with Let's Encrypt certificates
# ssl-default-bind-options ...
# (OCSP stapling configuration)

Let’s Encrypt shut down its OCSP responder on 2025-08-06 and stopped including OCSP URLs in issued certificates, so there is nothing to fetch and nothing to staple. Revocation is handled through CRL-based mechanisms in the browser instead. The reasoning is the same across every server on this site — see the nginx page, which covers what replaced it.

If your CA still runs a responder, stapling still works and is still worth having.

HAProxy wants the certificate, its chain and the private key concatenated into one PEM file, in that order — which differs from nginx and Apache and is a common first-time stumble:

Terminal window
cat fullchain.pem privkey.pem > /etc/haproxy/certs/site.pem
sudo chmod 400 /etc/haproxy/certs/site.pem
sudo chown root:root /etc/haproxy/certs/site.pem

Pointing crt at a directory loads every .pem inside it and selects by SNI, which is the usual arrangement for more than one site.