====== SSL Let's Encrypt automatisé (DNS-01 + API Gandi) — renouvellement sans intervention ======
by thenoiser / apo33
**Objectif** : remplacer le renouvellement manuel du certificat Let's Encrypt (challenge DNS-01 à copier-coller à la main tous les ~60-90 jours) par un système entièrement automatisé, piloté par l'API Gandi, avec un token valable jusqu'à 1 an.
Ce tutoriel fait suite à/remplace la méthode manuelle décrite dans **"Comment faire sa web radio"** (renouvellement manuel via ''certbot certonly --manual --preferred-challenges dns''). Le principe reste le DNS-01 (pas besoin du port 80 ouvert, fonctionne même derrière un reverse proxy ou sur une machine sans exposition web directe), mais la pose/suppression du challenge TXT est déléguée à l'API Gandi.
===== 1. Créer un jeton d'accès personnel (PAT) Gandi =====
Interface Gandi → Paramètres → **Jetons d'accès personnels** → Créer.
- **Nom** : un nom explicite (ex: ''certbot-dns-automation'')
- **Organisation** : **vérifier qu'on est bien positionné sur l'organisation propriétaire du domaine** (peut différer du compte personnel connecté — se tromper d'organisation donne un token qui "marche" mais ne voit pas le bon domaine, erreur 403 silencieuse)
- **Expire dans** : 1 an (maximum autorisé par Gandi)
- **Ressources du jeton d'accès** : "Restreint aux produits sélectionnés" → cocher uniquement le domaine concerné
- **Permissions**, section **Domaines** (bien descendre jusqu'à cette section précise — ne pas confondre avec la section "Organisation" tout en haut, dont les cases se ressemblent) :
* ✅ **Voir et renouveler les domaines** (lecture, indispensable — sans elle, l'API renvoie une erreur "Unable to get base domain")
* ✅ **Gérer la configuration technique des domaines** (écriture DNS, pour poser/retirer le challenge TXT)
- Ne rien cocher d'autre (Facturation, Web Hosting, Cloud, Certificats SSL, Boîte Mail — aucun rapport, principe de moindre privilège)
Copier le token immédiatement (affiché une seule fois).
**Test de validation rapide**, avant même de toucher à certbot :
curl -H "Authorization: Bearer VOTRE_TOKEN" \
https://api.gandi.net/v5/livedns/domains/votredomaine.org
Doit renvoyer du JSON avec les infos du domaine. Une erreur ''403 Forbidden'' = mauvaise organisation ou permissions incomplètes — retourner corriger le token (Gandi ne permet pas d'éditer un token existant, il faut le supprimer et en recréer un).
===== 2. Installer certbot + plugin Gandi (via pipx, isolé) =====
Ne pas utiliser ''pip install --break-system-packages'' en direct : conflit quasi garanti avec les paquets système (''cffi'' notamment). Utiliser ''pipx'', qui isole proprement l'environnement.
sudo apt install -y pipx
sudo pipx install certbot
sudo pipx inject certbot certbot-dns-gandi
sudo pipx ensurepath
Si un ''certbot'' système existe déjà (''/usr/bin/certbot'', installé via ''apt''), pipx ne le remplace **pas** automatiquement sur le PATH — il faut appeler explicitement le binaire pipx :
which certbot # montre souvent encore /usr/bin/certbot
sudo /root/.local/bin/certbot plugins # vérifie que dns-gandi apparaît bien ici
**Toujours utiliser ''/root/.local/bin/certbot'' explicitement** dans la suite de ce tuto (obtention, renouvellement, timer) pour être sûr de viser la bonne installation.
===== 3. Fichier de credentials =====
sudo mkdir -p /etc/letsencrypt
sudo nano /etc/letsencrypt/gandi.ini
dns_gandi_token = VOTRE_TOKEN
Le nom de la clé est **''dns_gandi_token''** (pas ''dns_gandi_api_token'', erreur courante qui donne "Missing property in credentials configuration file").
sudo chmod 600 /etc/letsencrypt/gandi.ini
sudo chown root:root /etc/letsencrypt/gandi.ini
===== 4. Obtenir le certificat =====
sudo /root/.local/bin/certbot certonly \
--authenticator dns-gandi \
--dns-gandi-credentials /etc/letsencrypt/gandi.ini \
--key-type ecdsa \
--deploy-hook "systemctl reload nginx" \
-d sousdomaine.votredomaine.org
Si l'erreur suivante apparaît :
Incorrect TXT record "..." found at _acme-challenge.sousdomaine...
Hint: ... try increasing --dns-gandi-propagation-seconds (currently 10 seconds)
C'est un simple problème de timing de propagation DNS, pas un problème de credentials. Relancer avec un délai plus long :
sudo /root/.local/bin/certbot certonly \
--authenticator dns-gandi \
--dns-gandi-credentials /etc/letsencrypt/gandi.ini \
--key-type ecdsa \
--dns-gandi-propagation-seconds 45 \
-d sousdomaine.votredomaine.org \
--force-renewal
Une fois un essai réussi avec un délai donné, cette valeur est **automatiquement sauvegardée** dans ''/etc/letsencrypt/renewal/.conf'' (''dns_gandi_propagation_seconds = 45'') — les renouvellements futurs en bénéficient sans réglage supplémentaire.
===== 5. Automatiser le renouvellement (timer systemd dédié) =====
Ne pas se fier au timer ''certbot.timer''/''snap.certbot.renew.timer'' déjà présent sur le système : il utilise le **certbot système** (''/usr/bin/certbot''), qui ne connaît pas le plugin ''dns-gandi''. Créer un timer dédié qui pointe explicitement vers le certbot pipx.
sudo nano /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot renewal (pipx, dns-gandi)
[Service]
Type=oneshot
ExecStart=/root/.local/bin/certbot renew --quiet
sudo nano /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Run certbot renewal twice daily
[Timer]
OnCalendar=*-*-* 03,15:00:00
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer
sudo systemctl list-timers | grep certbot-renew
Désactiver l'ancien timer système pour éviter tout conflit/doublon :
sudo systemctl disable --now certbot.timer 2>/dev/null
sudo systemctl disable --now snap.certbot.renew.timer 2>/dev/null
===== 6. Valider =====
sudo /root/.local/bin/certbot renew --dry-run
Doit afficher ''Congratulations, all simulated renewals succeeded''.
===== 7. Cas particulier : utiliser ce certificat sur un vieux serveur Apache (SME/CentOS) =====
**Section sensible.** Si le certificat doit être utilisé par un Apache ancien (ex: Apache 2.4.6 / OpenSSL 1.0.2k sur CentOS 7, cas du serveur SME), le fichier ''fullchain.pem'' moderne (certificat + tous les intermédiaires concaténés) peut faire planter ''mod_ssl'' au démarrage avec :
AH01903: Failed to configure CA certificate chain!
AH02312: Fatal error initialising mod_ssl, exiting.
Ce n'est pas un problème de contenu invalide, mais de format non supporté par cette version d'OpenSSL — **et comme SME régénère toute la config Apache d'un coup à chaque événement, cette erreur peut interrompre TOUS les sites du serveur**, pas seulement celui concerné.
==== 7.1 Séparer le certificat de la chaîne ====
Ne **jamais découper ''fullchain.pem'' à la main avec ''awk''/''sed''** (source d'erreurs de parsing PEM très facile) — utiliser directement les fichiers déjà séparés par certbot :
# Sur la machine où le cert a été obtenu :
sudo cat /etc/letsencrypt/live/sousdomaine.votredomaine.org/cert.pem # certificat seul (feuille)
sudo cat /etc/letsencrypt/live/sousdomaine.votredomaine.org/chain.pem # chaîne d'intermédiaires seule
sudo cat /etc/letsencrypt/live/sousdomaine.votredomaine.org/privkey.pem # clé privée
Copier ces trois fichiers tels quels vers le serveur cible (ex: via heredoc %%cat > fichier << 'EOF' ... EOF%%), dans un dossier dédié ne touchant à aucun certificat existant :
mkdir -p /etc/pki/tls/certs/sousdomaine/
# coller cert.pem, chain.pem, privkey.pem
chmod 600 /etc/pki/tls/certs/sousdomaine/privkey.pem
chmod 644 /etc/pki/tls/certs/sousdomaine/cert.pem /etc/pki/tls/certs/sousdomaine/chain.pem
chown root:root /etc/pki/tls/certs/sousdomaine/*
Valider en pur ''openssl'', sans toucher à Apache, avant toute intégration :
openssl x509 -in /etc/pki/tls/certs/sousdomaine/cert.pem -noout -subject -dates
openssl crl2pkcs7 -nocrl -certfile /etc/pki/tls/certs/sousdomaine/chain.pem | openssl pkcs7 -print_certs -noout
La deuxième commande doit lister 2-3 certificats intermédiaires proprement, sans erreur ''bad end line'' ni ''no start line''.
==== 7.2 Intégrer via un fragment templates-custom (SME) ====
Ne jamais éditer ''httpd.conf'' directement (régénéré automatiquement). Créer un fragment isolé, conditionné au domaine concerné uniquement :
mkdir -p /etc/e-smith/templates-custom/etc/httpd/conf/httpd.conf/WebAppVirtualHost
cat > /etc/e-smith/templates-custom/etc/httpd/conf/httpd.conf/WebAppVirtualHost/06SSLCertPersonnalise << 'EOF'
{
my $name = $domain->key;
if ($name eq 'sousdomaine.votredomaine.org') {
$OUT .= " SSLCertificateFile /etc/pki/tls/certs/sousdomaine/cert.pem\n";
$OUT .= " SSLCertificateKeyFile /etc/pki/tls/certs/sousdomaine/privkey.pem\n";
$OUT .= " SSLCertificateChainFile /etc/pki/tls/certs/sousdomaine/chain.pem\n";
}
}
EOF
La condition %%if ($name eq '...')%% isole strictement ce fragment à un seul domaine — aucun risque pour les autres vhosts, actuels ou futurs, qui utilisent le même template ''WebAppVirtualHost''.
==== 7.3 Régénérer SANS redémarrer, puis tester en isolation ====
# Backup de sécurité avant toute manip
cp /etc/httpd/conf/httpd.conf /root/httpd.conf.backup-$(date +%Y%m%d-%H%M%S)
# Régénère le fichier SANS toucher au service actif
/sbin/e-smith/expand-template /etc/httpd/conf/httpd.conf
httpd -t
**Test isolé obligatoire avant tout redémarrage réel** (voir détail complet dans le tuto PeerTube, section 7.5) :
cp /etc/httpd/conf/httpd.conf /tmp/httpd-test.conf
sed -i \
-e 's|^Listen 0\.0\.0\.0:80|Listen 0.0.0.0:8081|' \
-e 's|^Listen 0\.0\.0\.0:443|Listen 0.0.0.0:8444|' \
-e 's|^Listen \[::\]:80|Listen [::]:8081|' \
-e 's|^Listen \[::\]:443|Listen [::]:8444|' \
-e 's|^PidFile.*|PidFile /tmp/httpd-test.pid|' \
/tmp/httpd-test.conf
/usr/sbin/httpd -f /tmp/httpd-test.conf -DFOREGROUND &
sleep 3
jobs # "En cours d'exécution" = le certificat charge correctement
tail -15 /var/log/httpd/error_log
kill %1
Seulement si ce test réussit, appliquer pour de vrai :
systemctl restart httpd-e-smith.service # nom exact du service à vérifier au préalable
systemctl status httpd-e-smith.service --no-pager
curl -Ik http://domaineprincipal.org # confirmer que le site principal répond toujours
**En cas d'échec** (service qui refuse de démarrer) : supprimer immédiatement le fragment custom fautif, régénérer sans lui, relancer le service. Le site principal doit revenir en quelques secondes.
rm /etc/e-smith/templates-custom/etc/httpd/conf/httpd.conf/WebAppVirtualHost/06SSLCertPersonnalise
/sbin/e-smith/signal-event domain-modify sousdomaine.votredomaine.org
systemctl restart httpd-e-smith.service
===== 8. Checklist de maintenance annuelle =====
- Le token Gandi expire au bout d'1 an max → prévoir son renouvellement avant expiration (Gandi ne prévient pas forcément), sinon le prochain cycle de renouvellement certbot échouera silencieusement au prochain passage du timer
- Vérifier périodiquement ''sudo systemctl list-timers | grep certbot'' pour confirmer que le timer est toujours actif
- Si un certificat est copié manuellement sur une autre machine (cas 7.), penser à resynchroniser ce fichier à chaque renouvellement (~tous les 90 jours) — ce n'est pas automatique dans ce montage