Installer Certbot pour nginx sur Ubuntu 24.04
Installez Certbot avec apt ou snap, lancez certbot --nginx et évitez les doublons de timer. Découvrez aussi le piège du renouvellement bloqué sur le port 80.
Installer Certbot : apt ou snap
Sur Ubuntu 24.04, sudo apt install certbot python3-certbot-nginx installe un Certbot fonctionnel qui émet de vrais certificats Let's Encrypt approuvés publiquement. La documentation amont de Certbot recommande plutôt le snap. La différence est limitée : le snap suit les versions amont, tandis que le paquet d’archive suit la version fournie avec la LTS et reçoit les correctifs de sécurité.
Choisissez une seule méthode. Deux installations de Certbot signifient deux timers de renouvellement visant le même arbre /etc/letsencrypt. Celle que vous avez oubliée est alors susceptible de vous surprendre.
Avec apt :
sudo apt update
sudo apt install certbot python3-certbot-nginxCette commande installe /usr/bin/certbot, le plugin nginx, une paire certbot.service + certbot.timer, ainsi qu’une entrée /etc/cron.d/certbot sans effet sous systemd.
Avec snap :
sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotLe snap fournit son propre timer, snap.certbot.renew.timer. Supprimez le paquet apt avant d’installer le snap.
Les deux installations fonctionnent ensuite de la même manière, à l’exception du moment exact où elles lancent le renouvellement (voir la section Renouvellement ci-dessous). Certbot 2.x utilise par défaut des clés ECDSA (P-256). Utilisez --key-type rsa uniquement pour un client qui ne prend pas en charge ECDSA. Tout l’état est stocké sous /etc/letsencrypt : archive/ contient les vrais fichiers de clé et de certificat, live/ pointe vers les fichiers actuels avec des liens symboliques, renewal/ contient un fichier de configuration par certificat, et accounts/ contient la clé de compte ACME.
Ce que fait réellement HTTP-01 et pourquoi le port 80 est obligatoire
Le challenge HTTP-01 est un callback. Vous demandez à Let's Encrypt un certificat couvrant example.com ; le service résout le nom dans le DNS public, ouvre une connexion au port 80 à l’adresse trouvée, puis demande http://example.com/.well-known/acme-challenge/<token>. Votre serveur répond avec le contenu exact du token que Certbot vient d’écrire sur le disque. C’est tout le mécanisme. Trois conséquences en découlent et expliquent la plupart des échecs d’émission.
- Le port 80 doit être accessible depuis Internet, pas seulement depuis votre ordinateur portable. Une règle
ufw, un security group du fournisseur cloud ou le firewall de la console du VPS qui n’autorise que le port 443 bloque l’émission et tous les renouvellements suivants. - Le DNS doit déjà pointer vers ce serveur. Le serveur de validation effectue sa propre résolution depuis l’extérieur ; vos entrées
/etc/hostset le cache de votre navigateur ne changent rien pour lui. - Si vous publiez un enregistrement AAAA, IPv6 est essayé en premier. Let's Encrypt réessaie via IPv4 uniquement lorsque la connexion IPv6 expire. Une connexion IPv6 refusée n’est pas réessayée, et un enregistrement AAAA obsolète pointant vers un hôte qui accepte la connexion et sert autre chose provoque un échec définitif.
Les redirections sont autorisées : la validation suit une redirection HTTP vers HTTPS et ne tient pas compte du fait que le certificat de l’autre côté est absent, expiré ou auto-signé. En revanche, elle ne commence jamais ailleurs que sur le port 80. Certbot n’implémente pas TLS-ALPN-01 ; « utiliser uniquement le port 443 » n’est donc pas une solution.
Choisir un authenticator : --nginx, --webroot, --standalone
--nginx est le choix par défaut lorsque nginx est déjà en cours d’exécution et sert déjà le domaine. Certbot analyse votre configuration, ajoute temporairement un emplacement pour le challenge, recharge nginx, effectue la validation, puis écrit les directives TLS dans votre server block. Sans interruption de service.
sudo certbot --nginx -d example.com -d www.example.comPour un serveur fraîchement installé et une exécution automatisée :
sudo certbot --nginx \
-d example.com -d www.example.com \
--agree-tos -m ops@example.com --no-eff-email \
--redirect --non-interactive--webroot convient lorsque vous ne voulez pas que Certbot intervienne dans la configuration nginx, par exemple si vous la générez à partir d’un template, la conservez dans git ou la déployez avec Ansible. Certbot écrit uniquement le fichier de challenge dans un répertoire que vous servez déjà.
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"--standalone convient lorsqu’aucun service n’écoute sur le port 80 : un serveur de messagerie, une API qui n’accepte que les connexions sur 443 ou un script de premier démarrage exécuté avant l’installation de nginx. Certbot se lie lui-même au port 80 pendant quelques secondes. Si nginx est en cours d’exécution, cette commande échoue. Arrêtez-le le temps de l’exécuter :
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"Ces hooks sont enregistrés dans la configuration de renouvellement du certificat. Le même arrêt et redémarrage sont donc effectués automatiquement lors du renouvellement.
Un server block qui fonctionne avant et après la création du certificat
Le problème de dépendance circulaire est le suivant : nginx refuse de démarrer si ssl_certificate pointe vers un fichier qui n’existe pas, tandis que Certbot ne peut pas effectuer la validation si nginx est arrêté. Commencez par rendre le site accessible sur le port 80.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}Exécutez sudo nginx -t && sudo systemctl reload nginx, vérifiez que curl -I http://example.com/ répond depuis l’extérieur du serveur, puis demandez le certificat. Ensuite :
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Le préfixe ACME plus long est déjà prioritaire sur location /. return 301 ne reçoit donc jamais de requête de challenge. Le modificateur ^~ est utile dans d’autres cas : dès que ce préfixe correspond, nginx ignore entièrement les locations basées sur des expressions régulières. Une règle ajoutée ultérieurement, comme location ~ /\. { deny all; }, ne peut donc pas bloquer le challenge. Conserver cette location sur le port 80 permet aux renouvellements de continuer à fonctionner après le passage du reste du site en HTTPS uniquement.
Les deux blocks ci-dessus servent des fichiers depuis le disque. Si nginx sert plutôt de reverse proxy devant une application, location / devient un block proxy_pass. Le server block de reverse proxy, ligne par ligne couvre alors les en-têtes nécessaires à cette application, tandis que la location ACME et les directives TLS restent exactement identiques.
La syntaxe HTTP/2 dépend de votre version de nginx. Ubuntu 24.04 fournit nginx 1.24, qui attend la directive intégrée, listen 443 ssl http2;, et refuse de démarrer si vous lui fournissez la directive http2 on; séparée. Debian 13 fournit nginx 1.26, qui attend http2 on; et se contente d’indiquer que la forme intégrée est obsolète. Vérifiez d’abord avec nginx -v.
Faites pointer nginx vers live/, jamais vers archive/. Les liens symboliques live/ sont réaffectés à chaque renouvellement. Un chemin direct vers archive/ vous laisse donc utiliser un certificat qui finit par expirer.
Les wildcards impliquent DNS-01, et DNS-01 implique un plugin
Un certificat wildcard (*.example.com) ne peut pas être validé avec HTTP-01 : aucun nom d’hôte unique ne permet de récupérer un fichier. DNS-01 est la seule méthode possible : vous prouvez que vous contrôlez le domaine en publiant un enregistrement TXT _acme-challenge.example.com. Pour effectuer cette opération sans intervention, Certbot a besoin des identifiants API de votre fournisseur DNS. C’est précisément le rôle des plugins du fournisseur. Le guide complet sur les certificats wildcard explique le fonctionnement des enregistrements TXT et le piège du renouvellement en mode manuel ; la version courte pour Cloudflare suit.
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflareAvec apt, le paquet est sudo apt install python3-certbot-dns-cloudflare à la place. Les identifiants doivent se trouver dans un fichier accessible uniquement à root :
# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_hereLimitez le token aux droits de modification du DNS sur cette seule zone. Il donne accès à votre DNS ; traitez-le comme une clé cryptographique.
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'Mettez le wildcard entre guillemets pour que votre shell ne l’interprète pas comme un motif glob. DNS-01 résout également les cas auxquels HTTP-01 ne peut pas répondre : les certificats pour des hôtes sans port 80 public, un service interne, une machine accessible uniquement via un VPN WireGuard auto-hébergé sur un VPS, ou un panneau d’administration lié à une interface privée.
Renouvellement : les 90 jours, le timer et le deploy hook
Les certificats Let’s Encrypt sont valides pendant 90 jours. Le paquet apt (Certbot 2.9.0) les renouvelle lorsqu’il reste moins de 30 jours. Le snap suit la version actuelle de Certbot qui, depuis la version 4.1, demande également à Let’s Encrypt une date de renouvellement suggérée via ACME Renewal Info (ARI) et peut donc renouveler plus tôt ; pour un certificat Let’s Encrypt, cela signifie généralement un renouvellement vers les deux tiers de sa durée de validité. Dans les deux cas, vous disposez d’une fenêtre d’environ 30 jours pendant laquelle un renouvellement défaillant reste un problème corrigeable, et non une interruption de service. Les renouvellements devraient devenir plus fréquents à partir de l’année prochaine : le profil par défaut de Let’s Encrypt passera aux certificats de 64 jours le 10 février 2027, puis aux certificats de 45 jours le 16 février 2028. Let’s Encrypt n’envoie plus d’e-mails d’avertissement avant expiration. Personne ne vous préviendra : la surveillance vous incombe désormais.
Vérifiez le timer fourni par votre installation :
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renew parcourt toutes les configurations dans /etc/letsencrypt/renewal/, ignore celles qui ne doivent pas encore être renouvelées et renouvelle les autres avec les options enregistrées dans chaque configuration. Il s’agit des flags de l’exécution initiale, sauf si vous les avez modifiés depuis avec certbot reconfigure. C’est pourquoi la première exécution est importante : c’est celle qui est enregistrée.
Le renouvellement du fichier sur le disque ne suffit pas. nginx continue de servir l’ancien certificat chargé en mémoire jusqu’à ce qu’une commande lui demande de recharger sa configuration. Configurez une deploy hook une seule fois :
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shTout fichier exécutable dans renewal-hooks/deploy/ est exécuté après chaque renouvellement réussi. Le flag --deploy-hook fait la même chose pour un certificat donné et enregistre renew_hook = ... dans sa configuration de renouvellement. certbot --nginx recharge la configuration pour vous ; les configurations --webroot et --standalone ne le font pas. L’absence de hook explique exactement pourquoi un site peut servir un certificat expiré alors que certbot certificates signale correctement qu’un nouveau certificat est disponible. Tout autre logiciel qui lit le certificat au démarrage nécessite le même hook. Une application conteneurisée telle qu’une installation de Nextcloud VPS avec Docker, TLS et sauvegardes doit également définir ici sa propre étape de redémarrage ou de rechargement.
Tester le renouvellement en conditions réelles
sudo certbot renew --dry-runCette commande exécute le challenge complet avec l’environnement de staging de Let's Encrypt : même chemin de code, même pare-feu, même DNS, sans décompter quoi que ce soit des limites de débit de production (le staging applique ses propres limites, beaucoup plus généreuses) et sans enregistrer de certificats sur le disque. S’il réussit aujourd’hui, le renouvellement automatique dans 60 jours réussira également, à condition qu’aucun changement ne soit intervenu sur le serveur entre-temps.
Un dry run ne prouve pas que votre hook de reload est exécuté, car le comportement varie selon la version de Certbot. Testez cette partie manuellement : exécutez directement le script du hook, vérifiez que systemctl reload nginx réussit, puis contrôlez sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.
Les erreurs que vous rencontrerez réellement
Could not bind to IPv4 or IPv6., --standalone alors que nginx utilise déjà le port 80. Utilisez --nginx ou --webroot, ou arrêtez nginx pendant l’exécution. Vérifiez quel processus utilise le port avec sudo ss -lntp | grep ':80'.
Timeout during connect (likely firewall problem), Let's Encrypt n’a pas pu atteindre le port 80. Vérifiez les éléments de l’extérieur vers l’intérieur : sudo ufw status (ouvrez-le avec sudo ufw allow 'Nginx Full'), puis le firewall du fournisseur du VPS, puis le DNS. Testez depuis un autre système que votre serveur : curl -sSv http://example.com/.well-known/acme-challenge/test. Un enregistrement AAAA obsolète produit le même message.
unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, le port 80 est accessible, mais le token n’est pas servi. La requête est arrivée dans un autre server block (vérifiez lequel gère default_server), ou le répertoire transmis à -w n’est pas celui servi par nginx. Créez un fichier à l’emplacement /var/www/example.com/.well-known/acme-challenge/test et récupérez-le depuis l’extérieur ; s’il renvoie une erreur 404, le certificat n’était pas en cause.
DNS problem: NXDOMAIN looking up A for example.com, le nom ne se résout pas publiquement. Il peut s’agir de nouveaux enregistrements qui ne se sont pas encore propagés ou d’un enregistrement situé dans une zone que votre registrar ne sert pas.
too many certificates (5) already issued ..., une limite de débit et celle que l’on atteint souvent en boucle pendant le débogage. Le message indique la limite concernée et se termine par une durée de retry after. Let’s Encrypt limite à cinq par semaine les certificats en double portant exactement le même ensemble de noms. Cette limite ne peut pas être contournée : seul le temps permet de la lever. Par ailleurs, Let’s Encrypt autorise 50 nouveaux certificats par domaine enregistré et par semaine. Cette limite peut être augmentée en demandant une dérogation. Une demande portant exactement sur le même ensemble de noms qu’un certificat existant est considérée comme un renouvellement et n’est pas soumise à cette limite. Les renouvellements coordonnés via ARI ne sont soumis à aucune limite. Effectuez le débogage sur l’environnement staging avec --dry-run.
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, nginx est configuré pour utiliser un certificat qui n’a jamais été émis, ou un certificat supprimé avec certbot delete. Commentez le bloc server TLS, démarrez nginx, émettez le certificat, puis rétablissez le bloc.
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, ce fichier est fourni avec le paquet du plugin nginx. Sur une machine certonly sans python3-certbot-nginx, installez le plugin ou remplacez la ligne include par vos propres paramètres ssl_protocols et ssl_ciphers.
Gérer cela à grande échelle
Un certificat peut contenir jusqu’à 100 noms, et un seul certbot --nginx -d a.example.com -d b.example.com ... peut sembler tentant. Pourtant, un ancien enregistrement DNS qui échoue à la validation suffit à invalider tous les autres noms de ce certificat. Des certificats distincts par site échouent indépendamment les uns des autres. C’est ce qu’il faut sur un serveur qui héberge plus de quelques services. Au-delà de quelques sites, une passerelle compatible avec ACME devient utile : un reverse proxy Traefik qui exécute plusieurs applications avec Docker Compose demande et renouvelle lui-même les certificats, et Certbot disparaît complètement de l’architecture. Le choix du proxy à placer en frontal est une décision distincte. Comparer Nginx, Caddy et Traefik revient surtout à déterminer quelle part de la gestion des certificats et de la configuration par application vous souhaitez déléguer au proxy.
Sauvegardez intégralement /etc/letsencrypt, avec sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt et les liens symboliques préservés. Cette arborescence contient accounts/, votre clé de compte ACME, que vous ne pouvez pas régénérer à l’identique. La migration vers un nouveau VPS se résume alors à synchroniser l’arborescence avec -a, installer Certbot, modifier le DNS, puis exécuter certbot renew --dry-run avant la bascule.
Si vous reconstruisez le serveur ou passez à une nouvelle version LTS, le timer de renouvellement ne vous suit pas. Après toute migration, restauration d’un snapshot ou mise à niveau de la distribution, exécutez systemctl list-timers 'certbot*' ainsi qu’un --dry-run. Si vous omettez cette étape, un site peut devenir inaccessible 89 jours plus tard, à 3 heures du matin, avec un certificat que tout le monde pensait voir se renouveler automatiquement.
Les routes HTTP-01 présentées plus haut supposent que vous contrôlez la machine, qu’elle possède une IP publique et que le port 80 est ouvert sur Internet. Il s’agit donc d’un VPS. DNS-01 n’a besoin d’aucun de ces éléments. La procédure reste identique dans les deux cas.
Les mêmes étapes s’appliquent avec Apache au lieu de nginx. Lorsqu’un certificat public n’est pas possible, un certificat autosigné sur Ubuntu convient aux services internes.
FAQ
Dois-je laisser le port 80 ouvert si mon site ne sert que du HTTPS ?
Oui, pour le challenge HTTP-01. Let’s Encrypt commence toujours sa requête de validation sur le port 80. Certbot n’implémente pas TLS-ALPN-01. Un pare-feu qui n’ouvre que le port 443 bloque donc la première émission et chaque renouvellement automatique suivant. Une redirection du port 80 vers HTTPS convient : la validation la suit. Le seul moyen de ne pas utiliser du tout le port 80 est DNS-01, avec un plugin de fournisseur ou avec --manual --preferred-challenges dns de Certbot. Dans ce cas, vous ajoutez vous-même l’enregistrement TXT et le renouvellement n’est plus automatique, sauf si vous fournissez un script --manual-auth-hook.
apt ou snap : quel Certbot installer pour nginx sur Ubuntu 24.04 ?
Utilisez apt. sudo apt install certbot python3-certbot-nginx fournit Certbot 2.9.0 sur Ubuntu 24.04. Cette version est suffisamment récente pour tout le contenu de ce guide. Elle reçoit les correctifs de sécurité via unattended-upgrades et ne nécessite pas snapd. Choisissez snap uniquement si vous avez besoin immédiatement de la version la plus récente ou d’un plugin DNS distribué exclusivement comme snap. Dans tous les cas, choisissez une seule méthode : deux installations signifient deux timers de renouvellement qui ciblent le même arbre /etc/letsencrypt. L’installation oubliée est alors celle qui finit par poser problème.
Certbot peut-il émettre un certificat wildcard pour nginx ?
Uniquement avec DNS-01. Un wildcard comme *.example.com n’a pas de nom d’hôte unique depuis lequel récupérer un fichier de challenge. --nginx, --webroot et --standalone sont donc tous exclus. Installez le plugin de votre fournisseur DNS, placez un jeton d’API limité dans un fichier d’identifiants accessible uniquement par root, puis exécutez certbot certonly --dns-cloudflare -d example.com -d '*.example.com' en protégeant le wildcard entre guillemets pour empêcher le shell de l’interpréter comme un motif glob.
Pourquoi nginx sert-il encore l’ancien certificat après un renouvellement réussi ?
nginx conserve le certificat en mémoire et ne détecte pas le nouveau fichier sur le disque tant qu’il n’a pas rechargé sa configuration. certbot --nginx effectue ce rechargement, contrairement à --webroot et --standalone. Un renouvellement peut donc réussir alors que le navigateur affiche encore un certificat bientôt expiré. Déposez un script exécutable dans /etc/letsencrypt/renewal-hooks/deploy/, qui exécute nginx -t && systemctl reload nginx. Il sera lancé après chaque renouvellement réussi.
certbot renew --dry-run prouve-t-il que le renouvellement fonctionnera ?
En grande partie. Cette commande exécute le véritable challenge dans l’environnement de staging, avec le même pare-feu, le même DNS et le même chemin d’exécution. Elle ne touche pas aux limites de débit de production et n’enregistre aucun certificat sur le disque. Une réussite confirme donc que la partie réseau fonctionne. Elle ne prouve toutefois pas de manière fiable que votre deploy hook s’exécute. Testez-le séparément : exécutez le script du hook manuellement et vérifiez sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.