Certains liens de cette page sont des liens d'affiliation : si vous vous inscrivez via ces liens, ce site touche une commission, sans surcoût pour vous. Les prix et les recommandations ne changent pas.

Quatre sites sur une même machine ancienne se sont éteints. Pas lents, pas en 500 - éteints, au sens où le handshake TLS échouait avant même qu’une requête existe. Ils étaient en panne depuis cinq jours avant que quiconque le remarque, parce que rien ne surveillait la seule chose qui comptait.

La cause était banale : le renouvellement des certificats vivait dans un panneau de contrôle, la licence du panneau a expiré, et son cron de renouvellement quotidien est mort avec elle. Sept autres certificats sur la même machine étaient programmés pour expirer dans les trois semaines suivantes.

La distro était en fin de vie depuis des années, donc certbot ne s’installait pas proprement et les paquets du fournisseur avaient disparu depuis longtemps. Voici la recette qui a réglé ça en moins de trente minutes sans toucher un seul fichier de vhost, et la surveillance qui garantit que ça ne peut plus se reproduire en silence.

Pourquoi acme.sh et pas certbot

certbot est une application Python avec un arbre de dépendances. Sur une distro récente, ça ne pose pas de problème. Sur une distro en fin de vie, c’est tout le problème : le Python système est trop ancien pour les wheels modernes de cryptography, pip veut un compilateur que vous ne voulez pas sur une machine de production, et la voie snap demande un daemon que la distro n’a jamais livré.

acme.sh est un script shell. Il lui faut bash et soit curl soit wget. C’est toute la liste de dépendances. Il parle à l’API ACME en HTTPS via curl, donc même un OpenSSL ancien n’est pas dans le chemin de signature, et bash 4.2 est assez récent.

Il y a aussi une ironie plaisante à connaître si vous fuyez un panneau : plusieurs panneaux commerciaux utilisent acme.sh en coulisses. Vous ne remplacez pas le mécanisme, vous le reprenez à quelque chose qui a cessé de tourner.

Installer, et corriger la CA par défaut

curl https://get.acme.sh | sh -s email=you@yourdomain.dev
source ~/.bashrc

Faites ensuite immédiatement l’étape que la plupart des guides sautent. Depuis la version 3, acme.sh utilise par défaut ZeroSSL, pas Let’s Encrypt, et ZeroSSL demande un enregistrement de compte avec des identifiants EAB. Si vous vouliez Let’s Encrypt, dites-le une fois pour toutes :

acme.sh --set-default-ca --server letsencrypt
acme.sh --register-account -m you@yourdomain.dev

Sautez cette étape et votre première tentative d’émission échoue avec une erreur d’enregistrement qui ressemble à un problème réseau.

Émettre sans toucher les vhosts

L’objectif est zéro changement à la configuration du serveur web, parce que sur une machine comme celle-ci les configs sont générées par quelque chose que vous ne contrôlez plus et qu’une modification manuelle sera écrasée.

Deux choses rendent ça possible.

Un : utiliser la validation webroot dans le répertoire que le serveur sert déjà. Vérifiez d’abord si un chemin est déjà câblé - les panneaux en laissent généralement un derrière eux :

curl -s -o /dev/null -w '%{http_code}\n' \
  http://yourdomain.dev/.well-known/acme-challenge/test

Tout sauf une erreur de connexion signifie que le chemin est accessible et qu’il vous reste juste à trouver vers quel répertoire disque il pointe. Ensuite :

acme.sh --issue -d yourdomain.dev -d www.yourdomain.dev \
  -w /var/www/letsencrypt

Deux : écrire le certificat sous les noms de fichiers que le serveur attend déjà. Ne pointez pas la config vers le répertoire d’acme.sh ; pointez acme.sh vers les noms de fichiers de la config. --install-cert fait exactement ça, et le hook de reload le rend actif :

acme.sh --install-cert -d yourdomain.dev \
  --fullchain-file /etc/ssl/sites/yourdomain.dev.crt \
  --key-file      /etc/ssl/sites/yourdomain.dev.key \
  --reloadcmd     "nginx -s reload && apachectl graceful"

Le contenu des fichiers change, les chemins ne changent jamais, et rien dans la configuration du serveur web n’est modifié.

Les certificats expirés ne bloquent pas le renouvellement

L’inquiétude naturelle : chaque site redirige le HTTP vers le HTTPS, et le HTTPS est cassé, donc comment la validation atteint-elle le fichier de challenge ?

Ça fonctionne. Let’s Encrypt suit la redirection vers le HTTPS et ne valide pas le certificat pendant le challenge HTTP-01. Un certificat expiré depuis longtemps, un auto-signé, une incohérence de nom d’hôte - rien de tout ça n’empêche l’émission. Vous n’avez pas besoin de défaire les redirections d’abord, ce qui est une bonne chose, parce que sur une config générée vous ne pourriez pas de toute façon.

Automatiser, puis vérifier qu’il n’y a pas de rival

acme.sh --install-cronjob
crontab -l | grep acme

Cette deuxième ligne est la plus importante, et c’est l’erreur qu’on a faite. Le cron de renouvellement du panneau était désactivé - et un second cron de panneau faisant le même travail à une autre heure se trouvait quatre lignes plus bas, manqué au premier passage. Deux mécanismes de renouvellement qui se disputent les mêmes fichiers, c’est un problème qui ressurgit des semaines plus tard sous la forme d’un certificat valide sur le disque et périmé dans la mémoire du serveur.

Lisez tout le crontab. Commentez, ne supprimez pas, pour pouvoir voir ce qui s’y trouvait :

crontab -l > /root/crontab.bak-$(date +%Y%m%d)

acme.sh renouvelle à 60 jours par défaut, donc une exécution ratée dispose de 30 jours de tentatives avant que quoi que ce soit soit visible pour les utilisateurs. C’est une marge généreuse, et c’est exactement pour ça qu’un échec silencieux peut durer des mois.

Surveiller le certificat qui est servi, pas le fichier sur le disque

C’est la partie qui aurait détecté la panne initiale, et presque personne ne la met en place.

Vérifier le fichier ne suffit pas : ça vous dit que le renouvellement a écrit quelque chose, pas que le processus en cours l’a pris en compte. Un reload qui a échoué silencieusement laisse un fichier frais sur le disque et un certificat expiré en mémoire. Vérifiez le handshake :

#!/usr/bin/env bash
# days until the served certificate expires, per domain
for d in "$@"; do
  end=$(echo | openssl s_client -servername "$d" -connect "$d":443 2>/dev/null \
        | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  if [ -z "$end" ]; then echo "CRITICAL $d: no TLS handshake"; continue; fi
  days=$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 ))
  [ "$days" -lt 25 ] && echo "WARN $d: $days days left" || echo "OK $d: $days days"
done

Faites-le tourner chaque jour sur tous les noms d’hôte que la machine sert et alertez sous 25 jours. Deux propriétés en font le bon contrôle : il échoue bruyamment quand le handshake échoue complètement, ce qui est la vraie panne, et parce qu’il lit ce que le serveur remet à un client, un reload qui n’a pas eu lieu ne peut pas se cacher derrière un fichier renouvelé.

Ajoutez la fin du journal de renouvellement au même job et vous avez toute l’histoire dans une seule alerte :

tail -n 5 /var/log/acme-renew.log

Le résultat

Onze certificats réémis en une seule passe, tous valides pour les trois prochains mois, quatre sites morts de nouveau en ligne, renouvellement repris par un script shell de 200 Ko avec une entrée de cron qu’on peut lire. Aucun fichier de vhost n’a été modifié ; seuls les octets à l’intérieur des fichiers de certificat ont changé.

La leçon plus large ne concerne pas les clients ACME. C’est que l’expiration d’une chose n’est pas l’échec ; le silence l’est. Le renouvellement était cassé depuis environ deux mois avant que le premier site tombe, et la seule raison pour laquelle on l’a découvert, c’est un site qui s’est éteint devant des utilisateurs. Un contrôle de handshake de vingt lignes l’aurait signalé dès la première semaine.

Si vous montez une nouvelle machine plutôt que de secourir une ancienne, Caddy fait tout ça sans aucune configuration - on a détaillé cette pile dans Caddy et Docker en production. Cette recette est pour la machine dont vous avez hérité, celle où la config est générée, la distro est figée, et le seul changement acceptable est le plus petit possible.