Plan de l'article
💡 En résumé (TL;DR)
- On parle encore de « certificat SSL », mais le protocole moderne est TLS. Le certificat relie un nom de domaine à une clé publique et permet au client de vérifier le serveur.
- Pour une application Node.js sur VPS, la solution la plus simple est généralement de terminer TLS dans un reverse proxy comme Caddy, puis de joindre Node.js sur une interface privée.
- Let's Encrypt valide le contrôle du domaine via ACME. HTTP-01 exige que le domaine atteigne le serveur ; DNS-01 publie un TXT et permet les certificats wildcard.
- Le renouvellement doit être automatique et surveillé. Tester seulement l'émission initiale ne suffit pas.
- Le verdict AngleFormation : gardez Node.js non exposé, utilisez un proxy maintenu, faites confiance uniquement aux bons en-têtes proxy et vérifiez HTTPS depuis l'extérieur.
Un certificat expiré rend un site inaccessible ou déclenche un avertissement. Un proxy mal configuré peut provoquer des boucles de redirection, des cookies non sécurisés ou une fausse adresse client. Mettre HTTPS en production demande donc plus que l'obtention d'un fichier.
Ce guide présente une architecture simple pour un site Node.js ou Express déployé en Docker sur un VPS.
SSL, TLS, HTTPS et ACME
| Terme | Rôle |
|---|---|
| TLS | protocole de chiffrement et d'authentification |
| HTTPS | HTTP transporté dans TLS |
| Certificat | associe identité, clé publique, domaines et validité |
| ACME | protocole d'automatisation de l'émission et du renouvellement |
| Let's Encrypt | autorité de certification publique utilisant ACME |
| Reverse proxy | reçoit le trafic puis le transmet à l'application |
SNI permet à plusieurs domaines d'utiliser la même adresse avec des certificats différents. ALPN aide le client et le serveur à négocier le protocole, par exemple HTTP/2. Node.js documente ces fonctions dans son module TLS.
Étape 1 — Vérifier DNS et réseau
Avant toute émission :
- le domaine A ou AAAA doit pointer vers le bon serveur ;
- l'IPv6 publiée doit fonctionner ;
- les ports 80 et 443 doivent atteindre le proxy ;
- aucun autre service ne doit occuper ces ports ;
- le pare-feu de l'hébergeur et celui du VPS doivent les autoriser ;
- l'heure du serveur doit être correcte.
Vérifications :
dig A app.exemple.fr
dig AAAA app.exemple.fr
curl -I http://app.exemple.fr/
ss -lntp
Pour HTTP-01, Let's Encrypt recommande de laisser le port 80 accessible et de rediriger les requêtes ordinaires vers HTTPS. Si le site ne peut pas exposer le port 80 ou nécessite un wildcard, utilisez DNS-01 avec une automatisation DNS sécurisée.
Étape 2 — Choisir où terminer TLS
Option recommandée : reverse proxy
Le proxy :
- écoute sur 80 et 443 ;
- obtient et renouvelle les certificats ;
- redirige HTTP vers HTTPS ;
- applique les en-têtes ;
- transmet vers Node.js en privé.
Node.js peut aussi servir directement HTTPS avec le module node:https, mais l'application doit alors charger les clés, gérer le rechargement et les renouvellements. Pour plusieurs services, le proxy central est souvent plus simple.
Étape 3 — Déployer Node.js sans exposition publique
Compose minimal :
services:
app:
build: .
restart: unless-stopped
environment:
NODE_ENV: production
PORT: "3000"
ports:
- "127.0.0.1:3000:3000"
Le port écoute seulement sur la boucle locale de l'hôte. Si Caddy est lui-même dans Docker, n'utilisez pas localhost pour joindre un autre conteneur : Caddy rappelle que localhost désigne son propre conteneur. Placez les services sur un réseau Docker et utilisez le nom du service.
Étape 4 — Configurer Caddy
Sur l'hôte :
app.exemple.fr {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
Avec un nom public valide, Caddy active automatiquement HTTPS, obtient un certificat et gère son renouvellement. Il redirige aussi HTTP vers HTTPS selon ses valeurs par défaut.
Avant le rechargement :
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo journalctl -u caddy --since "10 minutes ago"
Les commandes supposent une installation système Caddy. Utilisez les chemins et droits de votre environnement.
Caddy et Docker sur le même réseau
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks: [web]
app:
build: .
restart: unless-stopped
expose:
- "3000"
networks: [web]
volumes:
caddy_data:
caddy_config:
networks:
web:
Caddyfile :
app.exemple.fr {
reverse_proxy app:3000
}
Les volumes Caddy doivent être persistants. Ne supprimez pas leur contenu pendant une mise à jour ordinaire.
Étape 5 — Configurer Express derrière le proxy
Express utilise trust proxy pour interpréter l'adresse client et le protocole transmis. La documentation avertit que la valeur true est dangereuse si le dernier proxy ne remplace pas correctement les en-têtes.
Si le proxy est local :
import express from "express";
const app = express();
app.set("trust proxy", "loopback");
app.get("/health", (req, res) => {
res.json({
ok: true,
protocol: req.protocol
});
});
app.listen(3000, "0.0.0.0");
Adaptez la liste de confiance à votre topologie exacte. S'il existe plusieurs chemins réseau, une simple profondeur en nombre de sauts peut être contournée.
Cookies
En production :
- Secure pour envoyer uniquement sur HTTPS ;
- HttpOnly pour réduire l'accès JavaScript ;
- SameSite selon le flux d'authentification ;
- domaine et chemin minimaux ;
- rotation de session.
Le proxy de confiance doit être correct pour qu'Express reconnaisse HTTPS et applique les cookies comme prévu.
Étape 6 — Choisir le défi ACME
| Défi | Fonctionnement | À choisir quand |
|---|---|---|
| HTTP-01 | fichier temporaire servi sur HTTP | site public simple, port 80 accessible |
| DNS-01 | TXT sous _acme-challenge | wildcard, serveurs multiples, port 80 indisponible |
DNS-01 demande des droits sur la zone DNS. Créez un jeton limité à la zone et aux opérations nécessaires. Ne donnez pas une clé globale au serveur si le fournisseur permet une portée plus restreinte.
Pour de nombreux frontends, Let's Encrypt explique que DNS-01 peut être plus facile, mais la propagation du TXT doit être prise en compte.
Étape 7 — Tester HTTPS
Depuis une autre machine :
curl -I http://app.exemple.fr/
curl -I https://app.exemple.fr/
openssl s_client -connect app.exemple.fr:443 -servername app.exemple.fr </dev/null
Vérifiez :
- redirection HTTP vers HTTPS ;
- nom de domaine présent dans le certificat ;
- chaîne de certification ;
- dates de validité ;
- aucun contenu mixte ;
- cookie Secure ;
- bonnes URL canoniques ;
- absence de boucle ;
- WebSocket si l'application l'utilise.
Testez depuis Internet et pas uniquement depuis le VPS.
Renouvellement et supervision
Un client ACME fiable renouvelle avant expiration. Ajoutez une alerte indépendante qui vérifie :
- jours restants ;
- statut HTTPS ;
- nom couvert ;
- erreurs du proxy ;
- échec répété de validation ;
- espace disque du volume de certificats.
Évitez de redemander sans cesse un nouveau certificat lors d'un test : Let's Encrypt applique des limites. Utilisez l'environnement de staging pour les essais de configuration et lisez les limites actuelles.
Ne dépendez pas d'une durée fixe dans vos procédures. Let's Encrypt propose plusieurs profils de durée et fait évoluer son infrastructure ; l'automatisation et la surveillance sont la bonne réponse.
Cas Cloudflare ou autre CDN
Lorsqu'un CDN se place devant le VPS, deux connexions existent :
- navigateur vers CDN ;
- CDN vers origine.
Chiffrez les deux. Évitez un mode qui accepte HTTP non authentifié vers l'origine. Limitez l'accès direct au VPS si possible, mais assurez-vous que le mécanisme ACME choisi continue de fonctionner.
Le certificat vu par le navigateur peut être celui du CDN, tandis que l'origine possède un certificat distinct.
En-têtes de sécurité
Le proxy ou l'application peut ajouter :
- HSTS après validation complète de HTTPS ;
- Content-Security-Policy adaptée au site ;
- X-Content-Type-Options ;
- Referrer-Policy ;
- Permissions-Policy.
N'activez pas HSTS avec une longue durée ou includeSubDomains avant de confirmer que tous les sous-domaines fonctionnent en HTTPS. Une erreur HSTS peut bloquer l'accès durablement côté navigateur.
Diagnostic des erreurs
Validation ACME échoue
- DNS pointe vers l'ancienne IP ;
- IPv6 incorrecte ;
- port 80 bloqué ;
- proxy non démarré ;
- WAF intercepte le challenge ;
- TXT DNS-01 non propagé ;
- limite d'émission atteinte.
Boucle de redirection
- l'application ne fait pas confiance au bon proxy ;
- le proxy transmet un protocole incorrect ;
- plusieurs couches redirigent contradictoirement ;
- une variable d'URL publique est en HTTP.
Mauvaise IP client
- trust proxy absent ou trop large ;
- en-tête transmis sans être nettoyé ;
- proxy supplémentaire non documenté.
Certificat expiré
- tâche de renouvellement arrêtée ;
- volume non persistant ;
- changement DNS ;
- permissions ;
- client ACME ou proxy obsolète.
Checklist de production
- A/AAAA corrects ;
- ports 80/443 maîtrisés ;
- application non exposée publiquement ;
- proxy et images épinglés ;
- certificat valide ;
- renouvellement automatique ;
- alerte avant expiration ;
- proxy de confiance précis ;
- cookies sécurisés ;
- HSTS activé seulement après validation ;
- sauvegarde de configuration ;
- procédure de rollback ;
- test après chaque changement DNS ou proxy.
FAQ
Let's Encrypt est-il gratuit ?
Oui pour l'émission publique, mais l'exploitation du serveur et l'automatisation restent à votre charge. Respectez ses limites et utilisez un client ACME maintenu.
Faut-il copier le certificat dans le conteneur Node.js ?
Pas si le reverse proxy termine TLS et joint Node.js sur un réseau privé. Cette architecture simplifie les renouvellements et évite de partager la clé avec l'application.
Comment obtenir un wildcard ?
Let's Encrypt exige le défi DNS-01 pour un wildcard. Automatisez la création du TXT avec un jeton DNS limité.
Peut-on fermer le port 80 après émission ?
Si vous utilisez HTTP-01, il reste nécessaire pour les validations et renouvellements. Let's Encrypt recommande de garder 80 ouvert pour le web et de rediriger vers HTTPS. DNS-01 suit une autre logique.
Conclusion
Le meilleur certificat est celui dont l'émission, le renouvellement et le diagnostic sont automatisés. Pour Node.js et Docker, un reverse proxy comme Caddy réduit la complexité : l'application reste privée, le proxy gère TLS et la configuration Express reflète précisément la topologie.
Pour choisir l'hébergement, consultez notre comparatif de VPS backend et notre pilier Tech-Cloud.