L'article décrit comment obtenir un certificat Let's Encrypt
Le programme fournira en sortie les certificats *.mondomain.tld et mondomain.tld. On pourra faire pointer nos services directement dessus ou les fournir aux différents serveurs.
Note: Un certificat Let's Encrypt a une durée de vie de 90 jours (64 jours à partir de 2027 puis 45 jours en 2028). Il est généralement renouvelé à 1/3 (ici 30 jours) avant son expiration. Dans mon cas mes services redémarrent au moins une fois par semaine, je ne me préoccupe donc pas de redémarrer les services au moment du changement de certificat, ceux-ci chargeront les nouveaux certificats de manière asynchrone au moment du redémarrage.
apt install python3 python3-venv curl dnsutils ca-certificates
On va créer un compte dédié à l'usage de certbot :
adduser --system --group --no-create-home --shell /usr/sbin/nologin certbot addgroup certssl-user
–system : Compte système, possède un UID/GID bas, afin de séparer des utilisateurs interactifs
–group : Crée un groupe éponyme
–no-create-home : N'aura pas de home directory, inutile ici
–shell /usr/sbin/nologin : Le binaire nologin empêche d'ouvrir un shell si on s'y connecte
L'utilisateur n'aura pas de mot de passe non plus, car inaccessible.
Le groupe certssl-user permettra d'autoriser qui peut consulter les certificats
Le dossier de travail sera /opt/certbot, on va créer le nécessaire à cet endroit. En l'occurrence un environnement virtuel python avec le package certbot installé
# Création du répertoire de travail mkdir /opt/certbot cd /opt/certbot # Création de l'environnement virtuel python3 -m venv . bin/pip install --upgrade pip # Installation de certbot bin/pip install certbot # certbot travaillera dans le dossier letsencrypt, on lui crée ses dossiers mkdir -p letsencrypt/{conf,work,log}
La structure est maintenant faite
Le service DNS de Hetzner servira de DNS intermédiaire pour nous fournir un sous-domaine avec une API DNS. Sur le site vous devrez :
On va réunir la configuration utilisée dans les scripts dans un fichier .env
# Domaine à certifier DOMAIN="mondomain.tld" # Token généré chez Hetzner TOKEN="MON-TOKEN-A-REMPLACER" # Adresse de contact pour Let's Encrypt en cas de besoin EMAIL="MON-EMAIL@mondomain.tld"
Ce script sera appelé par Certbot pour mettre à jour le challenge chez Hetzner.
#!/usr/bin/env bash set -euo pipefail set -a source .env set +a curl -sS --fail-with-body "https://api.hetzner.cloud/v1/zones/${DOMAIN}/rrsets/_acme-challenge/TXT/actions/add_records" \ --request POST \ --header "Content-Type: application/json" \ --header "Authorization: Bearer ${TOKEN}" \ --data "{ \"ttl\": 300, \"records\": [ { \"value\": \"\\\"${CERTBOT_VALIDATION}\\\"\", \"comment\": \"Certbot Script - $(date)\" } ] }" sleep 30
On n'oublie pas de le rendre exécutable
chmod +x hook-hetzner.sh
À noter que la variable CERTBOT_VALIDATION sera une variable d'environnement passée par certbot.
Pour valider le bon fonctionnement :
export CERTBOT_VALIDATION="Exemple de challenge de Certbot" ./hook-hetzner.sh unset CERTBOT_VALIDATION
Doit retourner :
{
"action": {
"id": XXXXXXX,
"status": "running",
"command": "set_rrset_records",
"progress": 0,
"started": "2026-04-24T15:41:15Z",
"finished": null,
"error": null,
"resources": [
{
"id": XXXXXXX,
"type": "zone"
}
]
}
}
Il est recommandé (mais pas obligatoire) de nettoyer l'enregistrement DNS après validation par Let's Encrypt. Tout comme pour le script précédent, Certbot propose d'appeler un script pour le nettoyage, que voici :
#!/usr/bin/env bash set -euo pipefail set -a source .env set +a curl -sS --fail-with-body "https://api.hetzner.cloud/v1/zones/${DOMAIN}/rrsets/_acme-challenge/TXT/actions/remove_records" \ --request POST \ --header "Content-Type: application/json" \ --header "Authorization: Bearer ${TOKEN}" \ --data "{ \"records\": [ { \"value\": \"\\\"${CERTBOT_VALIDATION}\\\"\" } ] }"
Le domaine principal doit pouvoir déléguer _acme-challenge.mondomain.tld vers Hetzner, ce qui est le rôle des enregistrements de type NS.
Sur votre console Hetzner, dans la partie DNS > mondomain.tld > Name servers, Hetzner indique les serveurs DNS qui contiennent notre enregistrement, dans mon cas : hydrogen.ns.hetzner.com., helium.ns.hetzner.de., oxygen.ns.hetzner.com. (Potentiellement différent chez vous)
Chez votre registrar, vous devrez ajouter ces serveurs comme enregistrement de type NS pour le sous-domaine _acme-challenge :
_acme-challenge IN NS hydrogen.ns.hetzner.com. _acme-challenge IN NS helium.ns.hetzner.de. _acme-challenge IN NS oxygen.ns.hetzner.com.
Pour valider le bon fonctionnement :
dig _acme-challenge.mondomain.tld TXT ;; ANSWER SECTION: _acme-challenge.gh3.be. 268 IN TXT "Exemple de challenge de Certbot"
Tout est en place pour que Certbot puisse travailler. Le certificat ayant une durée de vie courte, il est obligatoire d'automatiser son renouvellement. On crée un script également pour ceci :
#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(dirname "$0")" CONF_DIR="letsencrypt/conf/" WORK_DIR="letsencrypt/work/" LOGS_DIR="letsencrypt/log/" echo \> "$(date)": Starting renew script cd "${SCRIPT_DIR}" if [[ ! -f ".env" ]]; then echo "Fichier .env manquant" exit 1 fi set -a source .env set +a echo \> Renew \[*.\]${DOMAIN} bin/certbot --config-dir "${CONF_DIR}" --work-dir "${WORK_DIR}" --logs-dir "${LOGS_DIR}" \ certonly --verbose --non-interactive --agree-tos --manual --email "${EMAIL}" \ --preferred-challenges dns --domains "${DOMAIN}" --domains "*.${DOMAIN}" \ --manual-auth-hook ./hook-hetzner.sh \ --manual-cleanup-hook ./hook-hetzner-cleanup.sh echo \> Publish new *.pem files to directory cert/${DOMAIN} if [ ! -d "cert/${DOMAIN}" ]; then mkdir -p "cert/${DOMAIN}" fi cp -rfL "${CONF_DIR}/live/${DOMAIN}/"*.pem "cert/${DOMAIN}/" chmod 0640 "cert/${DOMAIN}/"*
Après l'avoir rendu exécutable, vous pouvez le lancer.
chmod +x certbot-renew.sh ./certbot-renew.sh
Mes tests ont été faits avec un utilisateur interactif par facilité. Mettons en place la sécurité nécessaire
# L'utilisateur certbot devient propriétaire du dossier (récursif) chown -R certbot: /opt/certbot # Droit restreint sur les fichiers et dossiers cd /opt/certbot find . -type d -exec chmod 750 {} \; find . -type f -exec chmod 640 {} \; chmod 700 *.sh chmod 700 bin/certbot chmod 600 .env # Le groupe certssl-user est autorisé sur le dossier cert chown -R certbot:certssl-user cert chown certbot:certssl-user .
Je trouve qu'une fois par semaine est un bon compromis. Dès qu'il restera 30 jours au certificat, il sera au maximum dans les 7 prochains jours.
Certbot ne tentera rien si le certificat est trop récent.
crontab -e -u certbot # Renouvellement certificat Let's Encrypt # Chaque dimanche à 2h14 14 2 * * 0 /opt/certbot/certbot-renew.sh > /opt/certbot/cron.log 2>&1
En cas de problème, le log sera disponible dans cron.log.
Le script n'est pas parfait, n'est pas hyper sécurisé et il y a forcément moyen de faire mieux/optimiser…
MAIS, je l'utilise depuis maintenant des années (un peu amélioré pour l'article et initialement avec DuckDNS), et il ne m'a jamais fait défaut.
CEPENDANT ! Il ne dispense pas d'un monitoring de l'état du certificat SSL sur le site web où il sera utilisé avec une alerte s'il expire prochainement (- de 15 jours), signe d'un problème.