Chapitre 619 minIntermédiaire
HTTPS sur Nginx : Let's Encrypt, Certbot et certificats internes
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- choisir entre autorité publique, autorité interne et certificat autosigné ;
- obtenir et renouveler automatiquement un certificat Let's Encrypt avec Certbot ;
- demander un certificat à l'autorité interne et faire confiance à sa racine ;
- configurer TLS, la redirection et HSTS dans Nginx ;
- vérifier un certificat et corriger les erreurs courantes.
3 exercices corrigés1 schéma
Passer un site en HTTPS, c'est installer sur le serveur web un certificat TLS : un fichier signé par une autorité de certification, qui associe le nom du site à une clé publique et permet au navigateur de vérifier qu'il parle au bon serveur avant de chiffrer les échanges. Ce chapitre montre comment obtenir et renouveler automatiquement un certificat gratuit de Let's Encrypt avec Certbot pour un site public, comment obtenir un certificat de l'autorité interne pour un service du réseau local, comment configurer TLS dans Nginx et comment vérifier un certificat. Le fonctionnement de la cryptographie elle-même (clés, signature, chaîne de confiance, poignée de main TLS 1.3) est expliqué dans le cours Cryptographie.
Quel certificat pour quel service
| Service | Qui doit lui faire confiance | Certificat | Chez Sorbier |
|---|---|---|---|
| Site public | N'importe quel navigateur sur Internet | Autorité publique : Let's Encrypt (gratuit, automatisé) ou une autorité commerciale | suivi.sorbier-logistique.fr sur srv-web |
| Service interne | Les postes et serveurs de l'organisation | Autorité interne de l'entreprise | glpi.ad.sorbier.example sur srv-glpi, signé par l'autorité AD CS Sorbier-AC |
| Maquette, test | Toi seul | Autosigné | Une VM de TP, jamais en production |
Une autorité publique ne délivre de certificat que pour un nom public, dont elle peut vérifier que tu le contrôles : jamais pour srv-glpi, glpi.ad.sorbier.example (non publié sur Internet) ou une adresse IP privée. Les noms internes passent donc par l'autorité interne, que les postes du domaine reconnaissent parce que son certificat racine leur est distribué par stratégie de groupe (cours Windows Server, chapitre sur l'autorité de certification AD CS).
Let's Encrypt et le protocole ACME
Let's Encrypt est une autorité de certification publique, gratuite et automatisée. Un logiciel client, ici Certbot, dialogue avec elle selon le protocole ACME (RFC 8555) pour prouver que tu contrôles le nom demandé, puis obtient et renouvelle le certificat sans intervention humaine.
La preuve la plus courante est le défi HTTP-01 :
| Défi | Preuve | Quand l'utiliser |
|---|---|---|
| HTTP-01 | Un fichier publié sur le port 80 du serveur, à l'adresse /.well-known/acme-challenge/ | Un serveur web joignable depuis Internet sur le port 80 : le cas le plus simple |
| DNS-01 | Un enregistrement TXT _acme-challenge.<nom> publié dans la zone DNS | Un certificat générique (*.sorbier-logistique.fr), ou un serveur non joignable depuis Internet ; demande une API chez l'hébergeur DNS |
| TLS-ALPN-01 | Une réponse spéciale pendant la poignée de main TLS, sur le port 443 | Quand seul le port 443 est ouvert ; peu de clients le gèrent |
La durée de validité. Les certificats de Let's Encrypt durent aujourd'hui 90 jours et Certbot les renouvelle vers les deux tiers de cette durée. Les règles du CA/Browser Forum, qui s'imposent à toutes les autorités publiques, réduisent pas à pas la durée maximale : 200 jours depuis le 15 mars 2026, 100 jours à partir du 15 mars 2027, 47 jours à partir du 15 mars 2029. Let's Encrypt passera à 64 jours le 10 février 2027, puis à 45 jours le 16 février 2028. Conséquence pour toi : un certificat public ne se renouvelle plus à la main, et un renouvellement programmé « tous les 60 jours » ne suffira bientôt plus. Laisse Certbot décider de la date.
Deux services disparus. Depuis 2025, Let's Encrypt n'envoie plus de courriel avant l'expiration d'un certificat (c'est à ta supervision de surveiller les dates) et ne publie plus de répondeur OCSP : la directive ssl_stapling de Nginx est inutile avec ses certificats, et produit l'avertissement "ssl_stapling" ignored, no OCSP responder URL in the certificate.
Obtenir un certificat avec Certbot
Les prérequis. Avant de lancer Certbot sur srv-web :
- le nom
suivi.sorbier-logistique.fra un enregistrement A, dans la zone DNS publique, vers203.0.113.17; - le pare-feu redirige les ports 80 et 443 de cette adresse vers
192.168.10.225(règles de la DMZ dans Cybersécurité des infrastructures) ; srv-webpeut joindre l'API de Let's Encrypt en HTTPS (acme-v02.api.letsencrypt.org) : si le filtrage sortant de la DMZ n'autorise que les dépôts de mises à jour, ajoute ce nom à l'alias ;- le server block du site existe, avec le bon
server_name(chapitre 2).
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d suivi.sorbier-logistique.fr
Certbot demande une adresse électronique (facultative, pour les annonces importantes) et l'acceptation des conditions d'utilisation, réalise le défi HTTP-01 en ajoutant temporairement un bloc location à Nginx, puis modifie le server block : il ajoute l'écoute en HTTPS, les chemins du certificat, et un second bloc qui redirige le port 80 vers HTTPS.
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/suivi.sorbier-logistique.fr/fullchain.pem
Key is saved at: /etc/letsencrypt/live/suivi.sorbier-logistique.fr/privkey.pem
This certificate expires on 2027-01-19.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for suivi.sorbier-logistique.fr to /etc/nginx/sites-enabled/suivi
Congratulations! You have successfully enabled HTTPS on https://suivi.sorbier-logistique.fr
| Fichier | Contenu |
|---|---|
/etc/letsencrypt/live/<nom>/fullchain.pem | Le certificat du site suivi du certificat intermédiaire : c'est lui que Nginx présente |
/etc/letsencrypt/live/<nom>/privkey.pem | La clé privée, lisible par root seulement : elle ne quitte jamais le serveur |
/etc/letsencrypt/renewal/<nom>.conf | Comment renouveler ce certificat (méthode, options) |
/etc/letsencrypt/options-ssl-nginx.conf | Les réglages TLS que Certbot inclut dans le server block |
Les fichiers de live/ sont des liens vers la dernière version, dans archive/ : après un renouvellement, Nginx trouve le nouveau certificat au même chemin.
Le renouvellement automatique. Le paquet installe un minuteur systemd qui lance certbot renew deux fois par jour ; Certbot ne renouvelle que les certificats proches de l'expiration, puis recharge Nginx.
systemctl list-timers certbot.timer
sudo certbot certificates # liste des certificats, dates d'expiration
sudo certbot renew --dry-run # simule un renouvellement contre l'environnement de test
Configurer TLS dans Nginx
Certbot écrit une configuration qui fonctionne. Il est utile de savoir l'écrire toi-même, pour un certificat interne ou pour la relire en recette. Le server block complet du site de suivi en HTTPS :
# Redirection de HTTP vers HTTPS
server {
listen 80;
listen [::]:80;
server_name suivi.sorbier-logistique.fr;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name suivi.sorbier-logistique.fr;
ssl_certificate /etc/letsencrypt/live/suivi.sorbier-logistique.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/suivi.sorbier-logistique.fr/privkey.pem;
# Le navigateur n'utilisera plus que HTTPS pour ce site pendant un an
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
root /var/www/suivi/public;
index index.php index.html;
access_log /var/log/nginx/suivi.access.log;
error_log /var/log/nginx/suivi.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm-suivi.sock;
}
location ~ /\. {
deny all;
}
}
| Directive | Rôle |
|---|---|
return 301 https://$host$request_uri | Redirection permanente vers la même adresse en HTTPS |
listen 443 ssl | Écoute en TLS sur le port 443 |
http2 on | Active HTTP/2 (depuis Nginx 1.25, cette directive remplace le paramètre http2 de listen) |
ssl_certificate | La chaîne : le certificat du site suivi des intermédiaires, jamais le certificat seul |
ssl_certificate_key | La clé privée |
Strict-Transport-Security | HSTS : même si l'utilisateur tape http://, le navigateur passe directement en HTTPS, sans requête en clair interceptable |
X-Content-Type-Options: nosniff | Le navigateur respecte le type annoncé d'un fichier au lieu de le deviner |
Les protocoles sont déjà réglés dans nginx.conf de Debian 13 : ssl_protocols TLSv1.2 TLSv1.3. Ne réactive jamais TLS 1.0 ou 1.1 « pour un vieux client » : ils sont obsolètes et les navigateurs les refusent.
Comme au port 80, un server block par défaut sur le port 443 évite de présenter le certificat de ton site à qui balaie les adresses IP. ssl_reject_handshake refuse la poignée de main sans avoir besoin de certificat :
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_reject_handshake on;
}
Un certificat de l'autorité interne
GLPI sera publié sur le réseau local sous le nom glpi.ad.sorbier.example, un alias (CNAME) de srv-glpi.ad.sorbier.example. Son certificat vient de l'autorité d'entreprise Sorbier-AC, avec le modèle SorbierServeurWeb, réservé au groupe GG_Serveurs_Web (cours Windows Server, modèles de certificats d'AD CS).
1. Sur srv-glpi, générer la clé et la demande de certificat (CSR). La clé privée est créée sur le serveur et n'en sort jamais ; seule la demande, qui contient la clé publique et les noms, voyage.
sudo mkdir -p /etc/ssl/glpi && cd /etc/ssl/glpi
sudo openssl req -new -newkey rsa:3072 -noenc \
-keyout glpi.key -out glpi.csr \
-subj "/CN=glpi.ad.sorbier.example" \
-addext "subjectAltName=DNS:glpi.ad.sorbier.example,DNS:srv-glpi.ad.sorbier.example"
sudo chmod 600 glpi.key
openssl req -in glpi.csr -noout -text | grep -A1 "Subject Alternative Name"
# X509v3 Subject Alternative Name:
# DNS:glpi.ad.sorbier.example, DNS:srv-glpi.ad.sorbier.example
Les navigateurs ne lisent que l'extension SAN (Subject Alternative Name) et ignorent le nom commun CN : un certificat sans SAN est refusé, même si son CN est juste. -noenc (l'ancien -nodes) laisse la clé sans phrase de passe, pour que Nginx puisse la lire au démarrage sans intervention ; elle est protégée par ses droits (600, propriétaire root).
2. Depuis le poste d'administration, soumettre la demande à l'autorité. Copie glpi.csr sur le poste d'administration (avec scp), puis, dans un terminal PowerShell ouvert avec un compte autorisé sur le modèle :
certreq -submit -attrib "CertificateTemplate:SorbierServeurWeb" glpi.csr glpi.cer
certreq propose de choisir l'autorité, puis enregistre le certificat délivré dans glpi.cer. Exporte aussi le certificat racine de l'autorité, dont les serveurs Linux auront besoin : dans certsrv.msc, Propriétés de l'autorité, onglet Général, Afficher le certificat, onglet Détails, Copier dans un fichier, format X.509 encodé en Base 64 (.cer), fichier sorbier-ac.cer.
3. Sur srv-glpi, installer le certificat et faire confiance à l'autorité. Copie glpi.cer et sorbier-ac.cer sur le serveur. Les fichiers au format Base 64 commencent par -----BEGIN CERTIFICATE----- : c'est le format PEM qu'attend Nginx. Un fichier binaire (DER) se convertit avec openssl x509 -inform DER -in glpi.cer -out glpi.crt.
sudo cp glpi.cer /etc/ssl/glpi/glpi.crt
# Le certificat racine, ajouté au magasin de confiance du système
sudo cp sorbier-ac.cer /usr/local/share/ca-certificates/sorbier-ac.crt
sudo update-ca-certificates
# 1 added, 0 removed; done.
openssl verify /etc/ssl/glpi/glpi.crt
# /etc/ssl/glpi/glpi.crt: OK
update-ca-certificates ne prend que les fichiers d'extension .crt du dossier /usr/local/share/ca-certificates/. Une fois la racine ajoutée, curl, PHP et les autres programmes du serveur font confiance à tout certificat signé par Sorbier-AC : c'est indispensable pour que GLPI se connecte à l'annuaire en LDAPS (chapitre 7).
Dans le server block de GLPI, les deux lignes deviennent :
ssl_certificate /etc/ssl/glpi/glpi.crt;
ssl_certificate_key /etc/ssl/glpi/glpi.key;
Avec une autorité à un seul niveau, le certificat du serveur suffit : les clients ont déjà la racine. Avec une autorité racine hors ligne et une autorité secondaire, concatène le certificat du serveur puis celui de l'autorité secondaire dans le fichier de ssl_certificate.
Le renouvellement. AD CS ne parle pas le protocole ACME : le certificat interne se renouvelle à la main, ou par un script, avant sa date d'expiration (la durée dépend du modèle, souvent un ou deux ans). Note la date dans la documentation et surveille-la dans ta supervision. Des autorités internes compatibles ACME existent (step-ca, par exemple) et permettent d'utiliser Certbot en interne ; elles sont plus rares dans un environnement Windows.
Un certificat autosigné, pour une maquette seulement
Pour un TP où tu n'as pas d'autorité, un certificat autosigné se crée en une commande :
sudo openssl req -x509 -newkey rsa:3072 -noenc -days 30 \
-keyout /etc/ssl/private/maquette.key -out /etc/ssl/certs/maquette.crt \
-subj "/CN=maquette.lab" -addext "subjectAltName=DNS:maquette.lab"
Le chiffrement est le même qu'avec un vrai certificat, mais rien ne prouve l'identité du serveur : le navigateur affiche un avertissement, et un utilisateur habitué à cliquer sur « Continuer » cliquera aussi devant le faux certificat d'un attaquant. En production, il n'a pas sa place, et à l'oral d'E6, « j'ai mis un certificat autosigné » appelle toujours la question « et en production ? ».
Vérifier un certificat et dépanner
# Le certificat présenté par un serveur : sujet, émetteur, dates, noms
openssl s_client -connect suivi.sorbier-logistique.fr:443 -servername suivi.sorbier-logistique.fr </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# La négociation complète, vue par un client
curl -vI https://glpi.ad.sorbier.example 2>&1 | grep -E 'SSL connection|subject|issuer|expire|HTTP/'
L'option -servername envoie le nom demandé dans la poignée de main (extension SNI) : sans elle, un serveur qui héberge plusieurs sites présente son certificat par défaut, et tu crois à tort qu'il est faux.
| Erreur côté client | Cause | Correction |
|---|---|---|
certificate has expired / NET::ERR_CERT_DATE_INVALID | Certificat expiré, ou horloge du client fausse | Renouveler ; vérifier certbot renew et l'heure des machines |
no alternative certificate subject name matches target host name / ERR_CERT_COMMON_NAME_INVALID | Le nom tapé n'est pas dans le SAN | Refaire la demande avec tous les noms utilisés (alias compris) |
unable to get local issuer certificate | Chaîne incomplète (intermédiaire absent), ou racine inconnue du client | fullchain.pem plutôt que cert.pem ; racine interne distribuée aux clients |
self-signed certificate | Certificat autosigné | Certificat d'une autorité reconnue |
nginx: [emerg] cannot load certificate key … Permission denied | Nginx ne lit pas la clé (droits) | Clé propriété de root, droits 600 : le processus maître de Nginx, lancé en root, la lit au démarrage |
La date d'expiration de chaque certificat est un indicateur à superviser : Zabbix propose un modèle qui la lit et alerte quelques semaines avant l'échéance (cours Supervision).
Exercices
Exercice 1 sur 3 Le bon certificat Réussi À revoir
Pour chaque service, dis quel type de certificat tu installes (autorité publique, autorité interne, autosigné) et justifie en une phrase : (a) l'interface web de pfSense, consultée depuis le réseau d'administration ; (b) recette.sorbier-logistique.fr, publié sur Internet pour un client qui teste la nouvelle version ; (c) une VM de TP montée pour une démonstration en classe ; (d) le serveur MariaDB de srv-glpi, joint par Grafana ; (e) l'intranet intranet.ad.sorbier.example.
Voir la réponseMasquer la réponse de l'exercice 1
| Service | Certificat | Justification |
|---|---|---|
| (a) pfSense | Autorité interne | Nom interne, consulté seulement par des postes du domaine qui connaissent Sorbier-AC |
| (b) recette publique | Autorité publique (Let's Encrypt) | Le client externe ne connaît pas l'autorité interne ; le nom est public, le défi HTTP-01 ou DNS-01 est possible |
| (c) VM de TP | Autosigné | Aucune autorité disponible, aucun utilisateur réel ; durée courte |
| (d) MariaDB | Autorité interne | Le client (Grafana sur srv-journaux) est un serveur interne qui fait confiance à Sorbier-AC et peut vérifier le nom |
| (e) intranet | Autorité interne | Nom interne ad.sorbier.example, non publié : aucune autorité publique ne le signerait |
Tu avais trouvé ?
Exercice 2 sur 3 La demande de certificat Réussi À revoir
Le nouvel intranet sera joint sous trois noms : intranet.ad.sorbier.example, intranet (nom court, utilisé par les postes grâce au suffixe DNS) et srv-intranet.ad.sorbier.example. Écris la commande openssl qui crée la clé et la demande, puis explique pourquoi le nom court pose problème.
Voir la réponseMasquer la réponse de l'exercice 2
sudo openssl req -new -newkey rsa:3072 -noenc \
-keyout intranet.key -out intranet.csr \
-subj "/CN=intranet.ad.sorbier.example" \
-addext "subjectAltName=DNS:intranet.ad.sorbier.example,DNS:srv-intranet.ad.sorbier.example,DNS:intranet"
Le nom court intranet n'est pas un nom complet : il n'a de sens que dans le réseau qui ajoute le suffixe ad.sorbier.example. Aucune autorité publique ne l'accepterait ; une autorité interne peut le signer, mais c'est une mauvaise habitude. La bonne pratique est de rediriger le nom court vers le nom complet (un server block server_name intranet; qui renvoie un return 301 https://intranet.ad.sorbier.example$request_uri;) et de n'utiliser que le nom complet dans les liens.
Tu avais trouvé ?
Exercice 3 sur 3 Le renouvellement qui a échoué Réussi À revoir
Le 3 mars, le site de suivi affiche une erreur de certificat expiré. sudo certbot renew répond : Certbot failed to authenticate some domains (authenticator: nginx). The Certificate Authority reported these problems: Domain: suivi.sorbier-logistique.fr Type: connection Detail: 203.0.113.17: Fetching http://suivi.sorbier-logistique.fr/.well-known/acme-challenge/…: Timeout during connect (likely firewall problem). Explique ce qui s'est passé, propose la correction, puis deux mesures pour que cela ne se reproduise pas.
Voir la réponseMasquer la réponse de l'exercice 3
Ce qui s'est passé. Les serveurs de Let's Encrypt n'arrivent pas à joindre le port 80 de 203.0.113.17 : le défi HTTP-01 échoue, donc le certificat n'est plus renouvelé depuis des semaines, jusqu'à son expiration. Cause probable : la redirection du port 80 vers srv-web a été supprimée sur le pare-feu (par exemple « puisque le site est en HTTPS »), ou une règle bloque désormais ce port.
Correction. Rétablir la redirection du port 80 de l'adresse publique vers 192.168.10.225 (Nginx y répond par une redirection vers HTTPS, sauf pour les fichiers du défi), puis sudo certbot renew et vérifier avec openssl s_client.
Prévention. Superviser la date d'expiration du certificat avec une alerte un mois avant (Zabbix, ou un contrôle externe) : Let's Encrypt n'envoie plus de courriel. Et envoyer le journal de Certbot (/var/log/letsencrypt/letsencrypt.log) ou l'état de l'unité certbot.service à la supervision, pour qu'un renouvellement en échec déclenche une alerte dès la première tentative ratée. On peut aussi passer au défi DNS-01, qui ne dépend pas du port 80.
Tu avais trouvé ?