# Certificats TLS sur owni État relevé le 27/09/2026. Deux mécanismes coexistent, et ce n'est pas un désordre : c'est une histoire. Ce document dit lequel gère quoi, où chercher quand un site passe en « connexion non sécurisée », et ce qui a déjà cassé. ## Qui gère quoi ### certbot — un certificat multi-domaines pour huit sites `/etc/letsencrypt/live/alpid.alpinux.org/` porte **un seul certificat couvrant dix noms** : ``` alpid.alpinux.org dolibarr.alpinux.org owni.alpinux.org alpinux.org gitea.alpinux.org wiki.alpinux.org autoconfig.alpinux.org installparty.alpinux.org www.alpinux.org cloud.alpinux.org ``` Huit vhosts le servent directement. Renouvellement : `certbot renew` chaque nuit à 3 h 00, par `cronwrap` (donc visible sur `admin.alpinux.org`). > **Dix noms sur les cent autorisés : le nombre n'est pas un problème, le > couplage l'est.** Un seul domaine dont le DNS casse ou dont la validation > échoue empêche le renouvellement des neuf autres. Et un nom couvert par un > SAN devient un cas particulier que les outils traitent mal — voir l'incident > `cloud` plus bas. ### ISPConfig et acme.sh — un certificat par site ISPConfig émet par acme.sh (`/root/.acme.sh/_ecc/`) et dépose une copie dans `/var/www/clients///ssl/-le.crt`. C'est cette copie que le vhost lit, jamais l'original. Concernés : `admin`, `messagerie`, `portail`, `static`. ## Le piège, et il a déjà mordu deux fois **ISPConfig ne rafraîchit sa copie que lorsqu'il *crée* le certificat.** Un renouvellement par certbot passe inaperçu : le vhost continue de servir l'ancien fichier jusqu'à son expiration, puis le site tombe. Re-sauvegarder le site dans l'interface ne suffit pas à déclencher la copie. | Quand | Quoi | Vu le | |---|---|---| | 1er août 2026 | `static` et `portail` perdent leur HTTPS | 5 septembre | | 4 août 2026 | `cloud` perd son HTTPS | 27 septembre | Cinq semaines, puis sept. C'est le délai qu'il faut pour que quelqu'un s'aperçoive qu'un site ne répond plus. ## Le dispositif qui l'évite aujourd'hui 1. **Le hook** `/etc/letsencrypt/renewal-hooks/deploy/ispconfig-cert.sh` s'exécute après chaque renouvellement certbot. Il itère sur `$RENEWED_DOMAINS` — **tous** les noms du certificat, SAN compris —, cherche `*/ssl/${domain}-le.crt` sous `/var/www/clients` et le met à jour. C'est le cas nominal. 2. **Le filet** `/usr/local/sbin/owni-certs-sync.sh`, chaque nuit à 4 h 20, rattrape ce que le hook n'a pas traité : hook absent lors d'un renouvellement passé, copie écrasée par ISPConfig, certificat posé à la main. Mode audit par défaut, `--fix` pour corriger, alerte à 21 jours. 3. **La surveillance** : depuis le 27/09/2026 ce script passe par `cronwrap`, donc son état remonte sur `admin.alpinux.org`. Auparavant il alertait par `MAILTO=root`, c'est-à-dire nulle part — l'expiration de `cloud` a été signalée chaque nuit pendant 54 jours sans que personne la voie. > Retenir de cette histoire : **une alerte qui n'atteint personne n'est pas une > alerte.** Le script fonctionnait parfaitement ; c'est sa sortie qui se > perdait. ## Diagnostiquer ### La seule méthode qui ne dépend pas du mécanisme Ce que voit un visiteur, quel que soit l'outil qui a émis le certificat : ```sh for d in messagerie.alpinux.org alpinux.org wiki.alpinux.org \ gitea.alpinux.org cloud.alpinux.org; do fin=$(echo | openssl s_client -servername $d -connect $d:443 2>/dev/null \ | openssl x509 -noout -enddate | cut -d= -f2) echo "$d : $(( ($(date -d "$fin" +%s) - $(date +%s)) / 86400 )) jours" done ``` Un `echo | openssl s_client` qui ne rend rien = TLS cassé, donc site inaccessible en HTTPS. ### L'audit complet, sur le serveur ```sh sudo /usr/local/sbin/owni-certs-sync.sh # audit, n'écrit rien sudo /usr/local/sbin/owni-certs-sync.sh --fix # corrige ce qu'il peut ``` Il rend `0` si tout est cohérent, `1` s'il subsiste une anomalie. Sa sortie distingue : - `ok` — à jour ; - `ORPHELIN` (majuscules) — **expiré**, c'est une panne ; - `orphelin` (minuscules) — inconnu de certbot mais valide, simple avertissement ; - `inutilisé` — un certificat existe et aucun service ne le lit. ### Quel certificat un vhost lit-il vraiment ```sh for f in /etc/apache2/sites-enabled/*.vhost; do cert=$(sudo awk '/SSLCertificateFile/ {print $2; exit}' "$f") [ -n "$cert" ] && printf "%-34s %s\n" "$(basename "$f")" "$cert" done ``` ### Ce que couvre un certificat multi-domaines ```sh sudo openssl x509 -noout -text -in /etc/letsencrypt/live/alpid.alpinux.org/fullchain.pem \ | grep -A1 "Subject Alternative Name" ``` ## Réparer un site dont le certificat a expiré Cas rencontré le 27/09/2026 avec `cloud.alpinux.org`. **1. Établir ce qui manque vraiment.** Le nom est-il couvert par un certificat valide existant ? S'il est dans le SAN, il n'y a rien à émettre — seulement à recopier. **2. Recopier le certificat valide à l'emplacement que le vhost attend**, en gardant l'ancien plutôt qu'en l'écrasant : ```sh D=/var/www/clients/client1/web19/ssl L=/etc/letsencrypt/live/alpid.alpinux.org sudo cp -a $D/cloud.alpinux.org-le.crt $D/cloud.alpinux.org-le.crt.expire-AAAAMMJJ sudo cp -a $D/cloud.alpinux.org-le.key $D/cloud.alpinux.org-le.key.expire-AAAAMMJJ sudo cp $L/fullchain.pem $D/cloud.alpinux.org-le.crt sudo cp $L/privkey.pem $D/cloud.alpinux.org-le.key sudo chmod 644 $D/cloud.alpinux.org-le.crt sudo chmod 600 $D/cloud.alpinux.org-le.key sudo apache2ctl configtest && sudo systemctl reload apache2 ``` **3. Vérifier de l'extérieur**, pas seulement que la commande a rendu la main. Une fois le fichier en place, **le hook le maintiendra** : il cherche `*/ssl/${domain}-le.crt` et ne peut mettre à jour que ce qui existe. C'est pourquoi cette réparation n'est pas un pansement. **Alternative propre**, si le domaine mérite son propre certificat : le réémettre depuis ISPConfig (*Sites → le site → SSL → Let's Encrypt*, décocher, enregistrer, recocher, enregistrer). ## Ce qui reste en suspens **Neuf certificats ISPConfig ne servent à rien.** Chaque site couvert par le SAN possède en plus un certificat à son nom, généré et jamais lu : ``` inutilisé wiki.alpinux.org, gitea.alpinux.org, alpid.alpinux.org, dolibarr.alpinux.org, installparty.alpinux.org, autoconfig.alpinux.org, alpinux.org, owni.alpinux.org, cloud… ``` Rien n'est cassé de ce fait, et les supprimer demanderait de vérifier un par un qu'aucun service — Postfix, Dovecot, pure-ftpd — ne les lit. Le script les signale précisément pour éviter qu'on cesse de mettre à jour un fichier qui sert ailleurs. **`owni-certs-sync.sh` raisonne par nom de dossier, pas par SAN.** Il cherche `/etc/letsencrypt/live//` et classe « orphelin » tout nom qui n'en a pas — même couvert par le SAN d'un autre. C'est pourquoi il a su *signaler* l'expiration de `cloud` sans pouvoir la *réparer*. Lui apprendre à lire les SAN le rendrait autonome sur ce cas. ## Le script n'est pas versionné `/usr/local/sbin/owni-certs-sync.sh` et le hook n'existent **que sur la machine**. Choix assumé : ce sont des rustines autour d'un comportement d'ISPConfig, et les versionner reviendrait à les pérenniser. Mais cela veut dire qu'un serveur reconstruit les perdra, avec la connaissance qu'ils portent — d'où ce document, qui doit suffire à les réécrire.