HAProxy termine TLS en attachant un frontend à un port avec le mot-clé ssl et une référence crt. La mécanique est simple ; la surface est large. Cet article couvre la configuration de production qu’on utilise : disposition du bundle PEM, défauts de cipher sains, ALPN pour HTTP/2, agrafage OCSP, et les étapes de vérification avant de pointer le vrai trafic vers le nouveau listener.
Comment vérifier
Avant la bascule, vérifiez que le listener présente bien le certificat que vous comptez, supporte les protocoles attendus et chaîne correctement.
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
openssl s_client -connect example.com:443 -servername example.com -alpn h2,http/1.1 < /dev/null
openssl s_client -connect example.com:443 -servername example.com -status < /dev/null | grep -i 'ocsp response'
sudo journalctl -u haproxy -n 100 --no-pager | grep -iE 'ssl|crt'
Le drapeau -alpn négocie h2 d’abord, puis http/1.1 ; la réponse montre ce que HAProxy a choisi. -status demande un agrafage OCSP — « OCSP Response Status: successful » confirme que HAProxy agrafe. Si vous voyez « no response sent » avec l’agrafage configuré, le cache OCSP est vide ou le répondeur est inaccessible.
Ce qui se passe
La pile TLS de HAProxy lit les fichiers crt comme des bundles PEM concaténés : certificat, chaîne intermédiaire et clé privée dans un fichier. (Optionnellement, une réponse OCSP dans un fichier voisin .ocsp et un bloc de paramètres DH.) Les options par bind vivent sur la ligne bind ; les défauts qui s’appliquent à tous les binds vivent dans global sous ssl-default-bind-*.
Le flux sur un frontend TLS :
- Le client se connecte au port 443.
- HAProxy lit le ClientHello, choisit un certificat par SNI (correspondance avec les entrées
crt-listou le fichier danscrt). - La poignée de main TLS se termine ; ALPN négocie h2 ou http/1.1.
- HAProxy parse la requête HTTP et applique les ACL /
use_backend.
Deux séparations importantes :
- Par listener vs global :
ssl-default-bind-optionsest le plancher. Unbindparssl-min-ver TLSv1.3ne remonte le plancher que pour ce listener-là. - Côté front vs côté back : terminer à HAProxy et rechiffrer vers le backend sont deux configs indépendantes. La plupart des clients terminent à HAProxy et transmettent en HTTP sur un VLAN privé ; le rechiffrement est optionnel et se configure dans la ligne
serveravecssl verify required ca-file ....
La procédure
-
Définir des défauts globaux sains. Mettez ceci dans
globalpour que toute nouvelle lignebindhérite d’une base sécuritaire :global ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets tune.ssl.default-dh-param 2048ssl-default-bind-cipherscontrôle les ciphers TLS 1.0-1.2 ;ssl-default-bind-ciphersuitescontrôle TLS 1.3. Ce sont deux chaînes distinctes — ne définir que la première laisse TLS 1.3 sur les défauts intégrés. -
Construire le bundle PEM. Depuis un Let’s Encrypt ou une CA commerciale, concaténer certificat final, chaîne intermédiaire et clé privée :
sudo mkdir -p /etc/haproxy/certs sudo cat fullchain.pem privkey.pem > /etc/haproxy/certs/example.com.pem sudo chmod 640 /etc/haproxy/certs/example.com.pem sudo chown root:haproxy /etc/haproxy/certs/example.com.pemMode 640 avec le groupe
haproxyrend la clé illisible aux autres utilisateurs. Possession root et lecture seule pour le groupe haproxy : c’est le motif de production. -
Attacher le frontend avec TLS. Listener à un seul certificat avec ALPN :
frontend fe_https bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1 http-request redirect scheme https code 301 unless { ssl_fc } default_backend be_appalpn h2,http/1.1est ce qui débloque HTTP/2 côté front. Sans cela, les navigateurs retombent à HTTP/1.1. -
Ajouter HSTS au niveau réponse. Une fois HTTPS opérationnel de bout en bout et que vous vous êtes engagé à rester en HTTPS, ajoutez HSTS — mais pas avant, et pas avec
preloadavant d’avoir vécu avecmax-ageau moins un mois :frontend fe_https bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1 http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains" default_backend be_app -
Plusieurs certificats via crt-list. Quand vous servez plusieurs domaines, utilisez une
crt-listafin de pouvoir ajouter des certificats sans modifier la lignebind:cat <<'EOF' | sudo tee /etc/haproxy/certs/crt-list.txt /etc/haproxy/certs/example.com.pem example.com www.example.com /etc/haproxy/certs/api.example.com.pem api.example.com EOFfrontend fe_https bind *:443 ssl crt-list /etc/haproxy/certs/crt-list.txt alpn h2,http/1.1Les noms d’hôtes en fin de ligne sont les motifs SNI contre lesquels HAProxy matche ; le CN/SAN du certificat doit également correspondre pour que les navigateurs l’acceptent.
-
Agrafage OCSP. HAProxy agrafe une réponse OCSP si un fichier
.ocspexiste à côté du.pem. Générez-la une fois et rafraîchissez sur minuterie :openssl ocsp -issuer chain.pem -cert cert.pem \ -url $(openssl x509 -in cert.pem -noout -ocsp_uri) \ -no_nonce -respout /etc/haproxy/certs/example.com.pem.ocsp echo "set ssl ocsp-response $(base64 -w0 /etc/haproxy/certs/example.com.pem.ocsp)" \ | sudo socat /run/haproxy/admin.sock -Rafraîchissez la réponse OCSP toutes les 24h via cron ; le chargement par l’API d’exécution rend la nouvelle agrafe vivante sans reload.
-
Valider de bout en bout. Les labs TLS (qualys, mozilla observatory) et un
openssl s_clientlocal confirment la poignée de main. Puis chargez le site dans un vrai navigateur et vérifiez le panneau réseau pour HTTP/2.
Pièges courants
- Un mauvais ordre de fichier dans le bundle PEM (clé avant cert, intermédiaire manquant) échoue au parse avec un « unable to load certificate » peu utile —
openssl x509 -in cert.pem -noout -textconfirme que le fichier est parsable comme cert. - Oublier
alpn h2,http/1.1signifie que les clients modernes obtiennent HTTP/1.1 même si les deux côtés supportent h2. Testez avecopenssl s_client -alpn h2. - HSTS avec
preloadest une porte à sens unique — une fois soumis à la liste preload, vous ne pouvez pas le retirer sans délais navigateur. Ajoutezpreloadseulement après un mois de HSTS stable. tune.ssl.default-dh-param 2048ne compte que pour les ciphersuites DH ; sur les lignes bind modernes avec ECDHE-only, les params DH sont inutilisés. Les définir reste inoffensif et compatible vers l’avant.- La permission du fichier bundle PEM doit être lisible par l’utilisateur
haproxy. Si vous avez copié le fichier en root avec mode 600, HAProxy démarre mais le bind échoue silencieusement à charger le cert et retombe sur un auto-signé par défaut.
Stack Harbor pilote la rotation TLS comme une préoccupation infogérée — Let’s Encrypt ou CA commerciale, renouvellement automatisé, rafraîchissement OCSP, rechargement à chaud via l’API d’exécution, et un rapport mensuel « expire dans 30 jours » qui fait remonter les certificats non auto-renouvelés. Pour les clients multi-régions avec des dizaines de certificats, c’est ce qu’on gère dans le cadre des Environnements multi-régions.