Pour renouveler un certificat sous HAProxy sans coupure, on obtient chaque
certificat auprès d'un client ACME externe, on assemble clé et chaîne (ce que
la leçon nomme le matériel) dans un PEM, puis on le charge par le socket
d'administration : set ssl cert dépose le matériel, commit ssl cert le
contrôle avant de le mettre en service.
Cette leçon s'adresse à qui sait déjà écrire un frontend et un backend
HAProxy. Elle enseigne le cycle complet autour de lego, un
client ACME indépendant de HAProxy : le chemin du défi, un certificat par nom
servi par SNI, le script de renouvellement et la façon d'en prouver l'effet.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »La leçon suit le cycle dans l'ordre où il se construit, du port 80 jusqu'à la reprise après redémarrage.
- Router le défi HTTP-01 jusqu'au serveur de défi du client ACME.
- Obtenir un certificat par nom et assembler le PEM que HAProxy attend.
- Servir deux noms par SNI et mesurer la sélection par un contre-test.
- Charger un certificat à chaud et constater qu'un matériel cassé est refusé.
- Écrire un script de renouvellement qui ne laisse fuiter aucune clé.
Prérequis
Section intitulée « Prérequis »Cette leçon suppose que vous maîtrisez la configuration de base de HAProxy et que vous savez ce qu'est un défi ACME. Les faits ont été établis sur HAProxy 3.4.7 et lego 5.5.2, les versions du lab.
- Les sections
global,defaults,frontendetbackend, et la validation parhaproxy -c: Installer HAProxy et lire ses sections de configuration. - L'ordre d'évaluation des règles et des ACL : Router par noms et chemins avec ACL et maps.
- Le protocole ACME, ses défis et ARI : Certificats automatiques.
- Le socket d'administration et
socat: Modifier l'état de HAProxy à chaud.
Cette leçon couvre-t-elle le client ACME intégré de HAProxy ?
Section intitulée « Cette leçon couvre-t-elle le client ACME intégré de HAProxy ? »Non : elle traite un client ACME externe, pas la section acme de HAProxy.
La leçon Publier en HTTPS avec HAProxy 3.4
décrit le client intégré, qui obtient et renouvelle lui-même les
certificats mais reste expérimental : il exige
expose-experimental-directives. Elle présente aussi crt-store, le choix du
certificat par SNI et le reload. Cette page ne les redit pas.
Son périmètre est différent. Le programme qui parle à l'autorité est lego,
hors de HAProxy et indépendant de sa version ; HAProxy ne fait que servir les
fichiers qu'on lui remet. Trois sujets sont propres à cette page : le cycle
d'un certificat (défi, émission, assemblage, chargement, reprise), la
sélection par SNI avec un certificat par nom et une crt-list, et le
chargement vérifié par le socket. Le principe d'un client externe est celui
de Nginx TLS ; seul le chargement est propre à
HAProxy.
Comment le défi HTTP-01 atteint-il le client ACME ?
Section intitulée « Comment le défi HTTP-01 atteint-il le client ACME ? »Le défi HTTP-01 se joue en clair sur le port 80, avant que le certificat
existe : une règle doit envoyer /.well-known/acme-challenge/ vers le serveur de
défi du client, sans passer par la redirection vers HTTPS. Le client lego ouvre
son serveur de défi sur 127.0.0.1:5002. L'autorité interroge le port 80 ;
sans route, la requête atteint l'application, qui ne connaît pas le
jeton, et l'émission échoue. Dans le lab, le port est déjà publié ; il
manque la route, une ACL et un backend dédié.
frontend public bind :80 acl defi path_beg /.well-known/acme-challenge/ use_backend acme if defi default_backend applicatif
backend acme server defi 127.0.0.1:5002Le piège apparaît avec la redirection. HAProxy joue toutes les règles
http-request avant de regarder un seul use_backend : un http-request redirect scheme https s'applique donc au défi, même si la ligne use_backend
est écrite au-dessus. La redirection porte la condition unless defi. Le
symptôme est trompeur : le client rapporte un 308 ou une erreur TLS sur un
port 443 qui n'existe pas encore, et l'on accuse l'autorité.
Dans quel ordre construire la configuration ?
Section intitulée « Dans quel ordre construire la configuration ? »On construit en trois temps : le frontend HTTP avec la route du défi, puis les
certificats, puis seulement le frontend HTTPS. HAProxy refuse de démarrer sur
une crt-list (la liste des certificats à servir) dont les fichiers
n'existent pas, et le service tombe alors entièrement, port 80 compris. Or c'est le port 80 qui sert le défi : écrire
d'emblée la configuration finale crée un verrouillage parfait, pas de
certificat sans défi, pas de défi sans démarrage.
Entre chaque temps, validez avec haproxy -c -f /etc/haproxy/haproxy.cfg,
puis appliquez par un reload. Après le premier temps, un curl sur le
chemin du défi doit atteindre le serveur de défi du client, pas
l'application.
Pourquoi un certificat par nom plutôt qu'un seul avec deux SAN ?
Section intitulée « Pourquoi un certificat par nom plutôt qu'un seul avec deux SAN ? »Un certificat unique portant les deux noms en SAN passerait la validation stricte des deux côtés et rendrait la sélection par SNI impossible à mesurer : HAProxy présenterait le même fichier quel que soit le nom demandé. Le SAN (Subject Alternative Name) est la liste des noms qu'un certificat couvre. Le SNI (Server Name Indication) est le nom que le client annonce au début de la connexion, et que HAProxy compare à cette liste pour choisir le certificat à présenter.
La recette demande donc un certificat par nom, alpha.pub.lab.test et
beta.pub.lab.test, soit un appel à lego par nom : un seul appel avec deux
options -d produirait un certificat unique à deux noms, ce que la recette
refuse. Les appels sont séquentiels, car le serveur de défi n'écoute que sur
un port. Le frontend HTTPS les référence par une crt-list, un fichier qui
énumère un certificat par ligne.
frontend securise bind :443 ssl crt-list /etc/haproxy/certs/liste.crt default_backend applicatif/etc/haproxy/certs/alpha.pub.lab.test.pem/etc/haproxy/certs/beta.pub.lab.test.pemLa liste dit quels fichiers sont servis : un fichier oublié dans un dossier
ne se met pas en service tout seul, ce qui arrive avec crt pointé sur un
répertoire. Le contre-test du lab présente ensuite le certificat d'un nom et
le fait valider contre l'autre : cela doit échouer.
Comment HAProxy veut-il son matériel, et qui peut le lire ?
Section intitulée « Comment HAProxy veut-il son matériel, et qui peut le lire ? »HAProxy attend un seul fichier PEM par certificat, qui concatène la clé privée
et la chaîne ; lego écrit des fichiers séparés, donc l'assemblage vous revient,
et c'est là que se décident les droits. Le client dépose <nom>.key et
<nom>.crt sous acme/certificates/. Le .crt contient déjà la chaîne :
l'autorité émet par un intermédiaire et lego le joint par défaut, donc ajouter
un second fichier d'émetteur dupliquerait un maillon.
umask 077cat acme/certificates/alpha.pub.lab.test.key \ acme/certificates/alpha.pub.lab.test.crt \ > /etc/haproxy/certs/alpha.pub.lab.test.pemUn PEM réduit à la feuille fait échouer la vérification sur « unable to get
local issuer certificate », message qui accuse la racine alors que la chaîne
manque ; un navigateur masque souvent le défaut en complétant la chaîne
lui-même, ce que openssl ne fait pas. Côté droits, la validation exige que
tout fichier portant une clé privée soit illisible par les autres. La clé du
compte ACME, rangée par lego sous son dossier de travail, vaut autant : qui
la détient demande des certificats au nom du compte. Fermez ce dossier à
root, et posez les PEM en 0640 root:root ; HAProxy les lit en root avant
d'abandonner ses privilèges.
Comment charger un certificat sans redémarrer, avec un contrôle avant service ?
Section intitulée « Comment charger un certificat sans redémarrer, avec un contrôle avant service ? »Le socket d'administration charge un certificat en deux temps, set ssl cert
puis commit ssl cert : HAProxy valide le matériel au commit, et s'il est
cassé il refuse et garde le certificat précédent. C'est la différence entre un
chargement vérifié et un simple chargement. Un systemctl restart charge
aussi le nouveau matériel, mais il coupe les connexions en cours et laisse
le service à terre si le matériel est mauvais.
Le schéma suivant montre un tour complet pour un nom. Suivez-le de haut en bas : le script lance lego, l'autorité vient chercher le jeton sur le port 80, que HAProxy route vers le serveur de défi, puis le script assemble le PEM et le charge en deux commandes.
Le dépôt passe par l'entrée standard de socat, jamais par un argument :
une ligne de commande est visible dans ps par tout le monde, le temps
qu'elle dure. La syntaxe << du socket prend un bloc terminé par une ligne
vide.
{ printf 'set ssl cert %s <<\n' "$pem" cat "$temporaire" printf '\n\n'} | socat -t5 stdio /run/haproxy/admin.sock
echo "commit ssl cert $pem" | socat -t5 stdio /run/haproxy/admin.sockIl reste trois gestes. Lisez la réponse du socket et cherchez-y error ou
unable to, comme le fait le corrigé du lab. Envoyez abort ssl cert avec
le chemin pour annuler une transaction refusée. Enfin, réécrivez le fichier du
disque avec le matériel accepté ; sans cela, le certificat en mémoire et
celui du disque divergent, et un redémarrage fait un retour en arrière
silencieux.
Pourquoi l'autorité refuse-t-elle parfois le renouvellement ?
Section intitulée « Pourquoi l'autorité refuse-t-elle parfois le renouvellement ? »Parce que lego cite le certificat précédent dans l'ordre de renouvellement
(c'est ARI, RFC 9773), et que l'autorité refuse si le compte demandeur n'est pas
celui qui a obtenu ce certificat. Ce lab a échoué sur cette erreur lors d'un
rejeu le 26 octobre 2026 : 403 unauthorized :: requester account did not request the certificate being replaced by this order. Le message accuse le
certificat alors que la cause est le compte.
ARI (ACME Renewal Information) laisse le client demander à l'autorité quand
renouveler, et joint à l'ordre un champ replaces qui désigne le certificat
remplacé. Un compte différent suffit alors à faire refuser l'ordre, et il
apparaît dès que le dossier de comptes du client est recréé. Dans le lab, la
cause est à l'autre bout : l'autorité de test garde ses comptes en mémoire,
si bien qu'après son redémarrage le compte conservé localement ne correspond
plus à rien chez elle. Les deux moitiés du couple doivent survivre ensemble.
Le corrigé écarte le refus par --ari-disable : un ordre neuf ne
remplace rien, il n'a pas à citer ce qu'il remplace. Le script garde aussi
e-mail et annuaire (l'URL dir qui liste les points d'accès de
l'autorité) identiques à chaque appel. Deux autres drapeaux
viennent de mesures du lab avec lego v5.5.2 :
--renew-force: en lego 5,runne demande pas un certificat neuf quand il en trouve un, il se comporte commerenew. Le client l'a dit : « Skip renewal: The certificate expires at 15:30:11Z, the renewal can be performed in 1m15s ». Il saute alors le renouvellement en rendant un succès.--no-random-sleep: lego étale les renouvellements par un délai aléatoire, qui dépasse la vie d'un certificat de cinq minutes.
Ces choix valent pour une autorité de test. En production, on ne force
pas et on garde le délai : renew ne dérange l'autorité que lorsque
l'échéance approche, et forcer à chaque passage épuiserait ses limites de
débit. Autre particularité de lego 5 : la commande vient en premier, ses
options ensuite. --server placé avant run fait sortir le client en « flag
provided but not defined » sans qu'il contacte l'autorité.
Comment écrire le script de renouvellement sans fuite de clé ?
Section intitulée « Comment écrire le script de renouvellement sans fuite de clé ? »Le script renouveler refait le cycle pour chaque nom et n'imprime jamais ce
qu'il écrit : pas de set -x, pas de PEM en argument, pas de clé dans la
sortie. La validation l'exécute, puis lit les journaux. Une clé dans un
journal est une clé publiée : le journal est collecté, archivé, recopié dans
un rapport d'incident, et la rotation ne la rattrape pas.
#!/usr/bin/env bashset -euo pipefailumask 077trap 'echo "echec a la ligne $LINENO : $BASH_COMMAND" >&2' ERR
TRAVAIL=/opt/pub-haproxy-tls-stable
for nom in alpha.pub.lab.test beta.pub.lab.test; do "$TRAVAIL/lego" run --server https://pebble:14000/dir \ --email ops@pub.lab.test --accept-tos \ --renew-force --no-random-sleep --ari-disable \ --http --http.address :5002 -d "$nom" \ > "$TRAVAIL/dernier-ordre-$nom.log" 2>&1 # Suite : assembler le PEM, set ssl cert, commit ssl cert, install.doneLe trap ... ERR nomme la ligne en échec : sous set -e, la première commande
qui échoue termine le script avec un code 1 et aucun message. La sortie de
lego va dans un fichier, dont le script doit afficher la fin avant de sortir en cas
d'échec. Pour constater que l'ordre a produit du matériel neuf,
comparez le numéro de série du .crt avant et après. Enfin, deux racines
existent : le client approuve pebble.minica.pem pour parler à l'API, et
pebble-root.pem sert à vérifier les certificats émis. Les confondre se lit
comme une panne réseau alors que c'est un refus de confiance.
Comment prouver que le cycle tient ?
Section intitulée « Comment prouver que le cycle tient ? »On le prouve par ce que le service présente, pas par ce que le disque
contient : une poignée de main openssl s_client avec le nom en SNI, lue avant
et après le script. Le fichier du disque et le certificat servi peuvent
différer d'un seul chargement oublié ; seul le second compte pour un visiteur.
echo | openssl s_client -connect 127.0.0.1:443 \ -servername alpha.pub.lab.test 2>/dev/null \ | openssl x509 -noout -serial -enddateLa validation du lab porte sur quatre contrôles, et sur eux seuls.
- Identité. Chaque nom valide strictement, chaîne et nom d'hôte, avec la racine de l'autorité et sans désactiver la vérification, puis atteint l'application. Le contre-test doit échouer. Contrôle critique.
- Cycle. La validation exécute
renouveleret exige que le numéro de série servi change pour les deux noms. Elle pousse ensuite un certificat volontairement invalide par le socket et exige qu'il soit refusé, le précédent restant servi et valide. - Secrets. Tout fichier portant une clé privée sous le dossier de travail ou
/etc/haproxy/certsest illisible pour les autres, et aucune clé n'apparaît dans/var/logni dans le journal système. Contrôle critique. - Reprise. Elle recharge HAProxy, rejoue l'identité, redémarre la machine,
puis rejoue l'identité et le renouvellement. Contrôle critique, dernier de
la série : un cycle qui dépendait d'un dossier sous
/runou d'une variable de votre session se voit là, et nulle part avant.
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab part d'une application publiée en HTTP seulement : ni certificat, ni
bind ssl, ni route pour le défi. Une autorité de test aux certificats de
cinq minutes, le client lego enveloppé dans un exécutable et sa racine sont
fournis, et /opt/pub-haproxy-tls-stable/recette.txt décrit noms, chemins et
socket. Vous servez les deux noms en HTTPS, chacun avec son certificat, et vous
livrez le script renouveler. La validation l'exécute, compare les
numéros de série, pousse un matériel invalide, cherche les clés dans les
journaux, puis recharge et redémarre avant de tout rejouer.
À retenir
Section intitulée « À retenir »Un cycle de certificats éprouvé se construit dans l'ordre et se prouve sur le service.
- Le client ACME est externe ; l'ACME intégré, expérimental, a sa leçon.
- La route du défi passe avant toute redirection, avec
unless defi. - On construit HTTP, certificats, puis HTTPS : une
crt-listaux fichiers absents empêche HAProxy de démarrer. - Un certificat par nom rend le SNI mesurable ; le contre-test doit échouer.
- Le chargement est vérifié :
set ssl cert,commit ssl cert, et un refus garde le certificat en service. - Un refus « requester account » vient d'ARI et d'un compte différent.
- Aucune clé dans un journal : pas de
set -x, pas de PEM en argument.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Ces questions vérifient que vous savez dire qui présente quel certificat selon le nom demandé, et ce qu'un chargement vérifié protège. Un cycle de renouvellement se juge sur ce qu'il fait quand il échoue, pas seulement quand il réussit.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Publier en HTTPS avec HAProxy
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications