Table of Contents

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.

<note important> 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. </note>

1. Créer un jeton d'accès personnel (PAT) Gandi

Interface Gandi → Paramètres → Jetons d'accès personnels → Créer.

  1. Nom : un nom explicite (ex: certbot-dns-automation)
  2. 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)
  3. Expire dans : 1 an (maximum autorisé par Gandi)
  4. Ressources du jeton d'accès : “Restreint aux produits sélectionnés” → cocher uniquement le domaine concerné
  5. 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)
  6. 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).

<note tip> 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). </note>

2. Installer certbot + plugin Gandi (via pipx, isolé)

<note warning> 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. </note>

sudo apt install -y pipx
sudo pipx install certbot
sudo pipx inject certbot certbot-dns-gandi
sudo pipx ensurepath

<note important> 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. </note>

3. Fichier de credentials

sudo mkdir -p /etc/letsencrypt
sudo nano /etc/letsencrypt/gandi.ini
dns_gandi_token = VOTRE_TOKEN

<note important> Le nom de la clé est dns_gandi_token (pas dns_gandi_api_token, erreur courante qui donne “Missing property in credentials configuration file”). </note>

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

<note tip> 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/<domaine>.conf (dns_gandi_propagation_seconds = 45) — les renouvellements futurs en bénéficient sans réglage supplémentaire. </note>

5. Automatiser le renouvellement (timer systemd dédié)

<note important> 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. </note>

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)

<note warning> 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é. </note>

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

<note tip> 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. </note>

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

<note important> 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

</note>

8. Checklist de maintenance annuelle

  1. 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
  2. Vérifier périodiquement sudo systemctl list-timers | grep certbot pour confirmer que le timer est toujours actif
  3. 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