Câbles réseau bleus branchés sur une baie de serveurs, image du rôle d'aiguillage d'un reverse proxy

Nginx reverse proxy avec Docker Compose : le tutoriel HTTPS pas à pas (2026)

L’essentiel en une minute

Un Nginx reverse proxy reçoit les requêtes des clients à la place de votre application, termine le chiffrement HTTPS, puis transmet chaque requête au bon service interne. Avec Docker Compose, c’est le seul conteneur qui publie des ports : votre API reste invisible depuis l’extérieur.

Ce tutoriel place nginx devant l’API Node.js de notre guide pour créer une API REST avec Node.js et Express, qui écoute sur le port 3000 et expose une route /health, conteneurisée comme dans notre guide Docker pour débutants. Il repose sur la documentation de nginx.org, de Docker, de Certbot et de Let’s Encrypt, consultée le 30 octobre 2026. Les fichiers suivent ces documentations, mais nous ne les avons pas exécutés : la méthode est détaillée en fin d’article.

Points clés

  • nginx 1.30.6 (stable) et 1.31.7 (mainline) ont corrigé cinq vulnérabilités le 14 octobre 2026.
  • Quatre en-têtes sont à transmettre : Host, X-Real-IP, X-Forwarded-For et X-Forwarded-Proto. Côté Express, TRUST_PROXY=1 lit l’adresse du client un saut plus loin.
  • Une location qui définit un seul proxy_set_header perd tous ceux du niveau supérieur : c’est le piège classique des WebSocket.
  • Les certificats Let’s Encrypt du profil par défaut, classic, passeront de 90 à 64 jours le 10 février 2027, puis à 45 jours le 16 février 2028 : le renouvellement doit être automatique.
  • Les erreurs 502, 504 et 413 ont chacune une cause typique, visible dans les journaux.

Reverse proxy, proxy direct, répartiteur de charge

Un reverse proxy agit pour le compte des serveurs, alors qu’un proxy direct agit pour le compte des clients. MDN résume la différence ainsi : un proxy direct peut masquer l’identité des clients, un reverse proxy masque celle des serveurs, et sert à la répartition de charge, à l’authentification, au déchiffrement ou au cache (MDN, serveurs proxy et tunnels, consulté le 30 octobre 2026).

Le répartiteur de charge est une fonction qu’un reverse proxy assure quand plusieurs instances tournent derrière lui : sans méthode précisée, nginx répartit à tour de rôle (round-robin), avec least_conn et ip_hash comme alternatives (nginx.org, répartition de charge).

Proxy direct (forward) Reverse proxy Répartiteur de charge
Agit pour Les clients Les serveurs Les serveurs
Placé Côté réseau du client Devant l’application Devant plusieurs instances
Usage typique Filtrage, cache sortant, anonymat TLS, routage par domaine ou chemin, en-têtes Répartir le trafic, tolérer une panne
Exemple Proxy d’entreprise nginx devant une API nginx avec un bloc upstream

Pour une seule API, le Nginx reverse proxy apporte un point d’entrée HTTPS unique, des en-têtes fiables et un endroit où régler délais et limites.


Les versions à utiliser en octobre 2026

Le 14 octobre 2026, nginx.org a publié la 1.30.6 stable et la 1.31.7 mainline avec les mêmes cinq correctifs de sécurité : CVE-2026-15176 (alias combiné à rewrite), CVE-2026-93479 (expressions avec variables), CVE-2026-95101 (corps de requête en HTTP/3), CVE-2026-97629 (rotation des clés de tickets TLS) et CVE-2026-97205 (SSI avec AIO). La 1.31.8 du 21 octobre n’apporte ensuite que des changements et corrections de bugs (CHANGES-1.30 et CHANGES, consultés le 30 octobre 2026).

Composant État au 30 octobre 2026 Image Docker utilisée
nginx stable 1.30.6 nginx:1.30.6-alpine
nginx mainline 1.31.8 nginx:1.31.8-alpine
Certbot 5.9.0, publiée le 28 octobre 2026 certbot/certbot:v5.9.0
Node.js 26 LTS depuis le 28 octobre 2026 (26.13.0) Non utilisée ici
Node.js 24 LTS en maintenance depuis le 20 octobre 2026, jusqu’au 30 avril 2028 node:24-alpine

Les étiquettes viennent de la page de l’image nginx sur Docker Hub, la version de Certbot de son journal des modifications, et les dates de Node.js du calendrier officiel des versions. Nous gardons node:24-alpine, l’image de nos deux tutoriels : la ligne 24 reste prise en charge et l’API exige Node.js 24 au minimum.


Étape 1 : la configuration minimale d’un Nginx reverse proxy

La directive centrale est proxy_pass, qui indique l’adresse du serveur vers lequel nginx transmet la requête. Voici le fichier nginx/conf.d/api.conf de départ, en HTTP seulement :

server {
    listen 80;
    server_name api.example.com;

    # Servira au défi Let's Encrypt de l'étape 3
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        proxy_pass http://api:3000;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

api est le nom du service dans Docker Compose, et 3000 le port interne de l’API. L’ordre des deux blocs location n’a pas d’importance : parmi les préfixes, nginx retient celui qui correspond le plus longuement à l’adresse demandée (module core HTTP).

Les quatre en-têtes ne sont pas décoratifs. Par défaut, nginx remplace l’en-tête Host par $proxy_host, c’est-à-dire api:3000 ici, et l’application ne verrait jamais le vrai domaine (module proxy de nginx, consulté le 30 octobre 2026).

En-tête Valeur envoyée Ce que l’application en tire
Host $host, le domaine demandé Les liens absolus et le routage par domaine
X-Real-IP $remote_addr, l’adresse vue par nginx L’adresse du client, en une seule valeur
X-Forwarded-For La valeur reçue, plus $remote_addr à la fin La chaîne des adresses traversées
X-Forwarded-Proto $scheme, http ou https Savoir si le client était en HTTPS

Le piège de la barre oblique finale

Une barre oblique de trop dans proxy_pass change l’adresse transmise : si proxy_pass contient une URI, même réduite à /, la partie qui correspond au bloc location est remplacée par cette URI ; sans URI, la requête part telle que le client l’a envoyée.

Avec location /api/ et proxy_pass http://api:3000/;, une requête vers /api/v1/taches arrive sous la forme /v1/taches. L’API du tutoriel attend /api/v1/... : pas de barre finale.

Rester cohérent avec TRUST_PROXY

Notre API Express ne lit ces en-têtes que si la variable TRUST_PROXY est réglée. La page Express derrière un proxy explique qu’avec un nombre, Express fait confiance à ce nombre de sauts, en lisant X-Forwarded-For de droite à gauche. Avec un seul nginx devant, TRUST_PROXY=1 désigne la dernière adresse de la liste, celle que nginx ajoute lui-même : un client ne peut pas la falsifier.

La même page précise que req.protocol reflète X-Forwarded-Proto. Le cas d’un CDN devant nginx est traité plus bas.


Étape 2 : la pile Docker Compose

Trois prérequis : un serveur Linux avec Docker Engine et Compose, un enregistrement DNS A (et AAAA en IPv6) qui fait pointer api.example.com vers lui, et les ports 80 et 443 ouverts, le 80 servant au défi de Let’s Encrypt.

Seul nginx publie des ports, et c’est voulu. La page d’installation de Docker pour Ubuntu prévient que les ports publiés par des conteneurs contournent les règles des pare-feu ufw et firewalld (installer Docker Engine sur Ubuntu) : un port 3000 publié par erreur serait joignable malgré votre pare-feu.

Schéma de la pile Docker Compose : le navigateur se connecte au Nginx reverse proxy sur les ports 80 et 443, qui transmet en HTTP au service api sur le port 3000 interne, Certbot partageant les certificats par des dossiers montés

Voici l’arborescence, placée par exemple dans /srv/pile ; le dossier api-taches est le projet du tutoriel Node.js, avec le Dockerfile et le .dockerignore de notre guide Docker :

/srv/pile/
  compose.yaml
  .env                  JWT_SECRET, jamais dans Git
  api-taches/           package.json, src/, Dockerfile, .dockerignore
  nginx/conf.d/api.conf
  certbot/www/          défis Let's Encrypt
  certbot/conf/         certificats

Ce compose.yaml remplace celui du guide Docker, qui vivait dans api-taches et publiait 127.0.0.1:3000 : ici, l’API ne publie plus rien. Comme le dossier du projet change de nom (pile au lieu de api-taches), Compose crée un nouveau volume pile_api-data : la base SQLite du guide Docker n’est pas reprise. Rappel du Dockerfile multi-étapes de ce guide :

# syntax=docker/dockerfile:1
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:24-alpine
ENV NODE_ENV=production
ENV DB_PATH=/app/data/data.db
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
COPY src ./src
RUN mkdir -p /app/data && chown node:node /app/data
USER node
EXPOSE 3000
CMD ["node", "src/server.js"]

Puis /srv/pile/compose.yaml :

services:
  api:
    build: ./api-taches
    environment:
      JWT_SECRET: ${JWT_SECRET:?definir JWT_SECRET dans .env}
      DB_PATH: /app/data/data.db
      TRUST_PROXY: "1"
    volumes:
      - api-data:/app/data
    init: true
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    restart: unless-stopped

  nginx:
    image: nginx:1.30.6-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certbot/www:/var/www/certbot:ro
      - ./certbot/conf:/etc/letsencrypt:ro
    depends_on:
      api:
        condition: service_healthy
    restart: unless-stopped

  certbot:
    image: certbot/certbot:v5.9.0
    profiles: ["outils"]
    volumes:
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt

volumes:
  api-data:

Le secret vient du fichier .env placé à côté de compose.yaml : Compose le lit dans le dossier du projet pour remplacer ${JWT_SECRET}, et ${VAR:?message} arrête tout si la variable est vide (interpolation des variables). Le .env du tutoriel Node.js, rangé dans api-taches, n’est pas lu. Seules les variables listées sous environment entrent dans le conteneur (référence des services). Générez le secret sans installer Node.js sur le serveur :

cd /srv/pile
echo "JWT_SECRET=$(docker run --rm node:24-alpine node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))")" > .env

Les autres choix, tous tirés de la documentation de Docker :

  • Pas de clé ports sur l’API. C’est elle qui l’isole : Compose relie les services sur un réseau interne où chacun est joignable par son nom et par le port du conteneur (réseau dans Compose).
  • init: true. Un processus d’init transmet les signaux à Node.js, comme dans le guide Docker.
  • depends_on avec service_healthy. nginx attend que le test de santé, qui appelle /health, réussisse. fetch est stable depuis Node.js 21 (globales de Node.js 24).
  • restart: unless-stopped. La valeur par défaut, no, ne relance jamais un conteneur arrêté.
  • Le profil du service certbot. Il ne démarre pas avec docker compose up, mais docker compose run certbot le lance quand on le cible (profils).

Monter ./nginx/conf.d masque le default.conf de l’image. Créez certbot/www et certbot/conf, puis lancez docker compose up -d --build et curl -i http://api.example.com/health, qui doit répondre {"statut":"ok"}.


Étape 3 : obtenir et renouveler le certificat Let’s Encrypt

Le HTTPS pose un problème d’œuf et de poule : nginx refuse de démarrer si un bloc server cite un certificat absent, et Certbot a besoin de nginx pour prouver que vous contrôlez le domaine. C’est pourquoi le fichier de l’étape 1 sert déjà /.well-known/acme-challenge/ en HTTP : le défi HTTP-01 de Let’s Encrypt ne passe que par le port 80, à cette adresse (types de défis, consulté le 30 octobre 2026).

Lancez Certbot avec le greffon webroot, qui dépose un fichier temporaire dans le dossier partagé avec nginx (documentation de Certbot) :

cd /srv/pile
docker compose run --rm certbot certonly --webroot -w /var/www/certbot \
  -d api.example.com --email [email protected] --agree-tos --no-eff-email

La page d’installation de Certbot prévient que l’image Docker ne sait pas configurer le serveur web : seule la commande certonly est utilisable. Le certificat arrive dans certbot/conf/live/api.example.com/, vu par nginx sous /etc/letsencrypt/live/api.example.com/, avec fullchain.pem et privkey.pem. L’étape 5 les branche.

Automatiser le renouvellement

Le renouvellement doit être automatique, car les certificats raccourcissent. La FAQ de Let’s Encrypt indique des certificats de 90 jours par défaut. Son calendrier publié en décembre 2025 fait passer le profil par défaut, classic, à 64 jours le 10 février 2027, puis à 45 jours le 16 février 2028, et déconseille le renouvellement manuel.

Graphique en barres : durée des certificats Let's Encrypt du profil classic, 90 jours en octobre 2026, 64 jours au 10 février 2027 et 45 jours au 16 février 2028, avec le dernier tiers où Certbot renouvelle selon notre calcul

Depuis sa version 4.0.0, certbot renew renouvelle quand il reste moins d’un tiers de la durée de vie ; depuis la 4.1.0, il interroge aussi l’ARI (ACME Renewal Information), qui peut avancer la date (journal des modifications de Certbot). Selon notre calcul, sans ARI, cela donne un renouvellement à 30 jours de l’échéance aujourd’hui, à environ 21 jours avec 64 jours de validité et à 15 jours avec 45. Une tâche planifiée quotidienne sur l’hôte suffit :

# crontab de l'hôte, chaque jour à 3 h 17
17 3 * * * cd /srv/pile && docker compose run --rm certbot renew --quiet && docker compose exec -T nginx nginx -s reload

Le rechargement est indispensable, nginx gardant l’ancien certificat en mémoire. cd /srv/pile && docker compose run --rm certbot renew --dry-run teste le tout sur le serveur de test de Let’s Encrypt. Notre guide pour automatiser ses déploiements en CI/CD montre comment rejouer ces commandes depuis un pipeline.


Étape 4 : WebSocket, en-têtes de sécurité, délais et tailles

Cette étape explique chaque réglage et où il se place ; l’étape 5 les assemble dans un seul fichier. Les fichiers de conf.d sont inclus dans le bloc http du nginx.conf des paquets de nginx.org (pkg-oss) : une directive écrite hors de tout bloc server y est donc en contexte http.

Les WebSocket

Une connexion WebSocket commence par une requête HTTP qui demande un changement de protocole. La page WebSocket de nginx.org explique que Upgrade et Connection sont des en-têtes « de saut à saut » (notre traduction), donc non transmis : il faut les poser avec un map, autorisé seulement en contexte http (module map), et deux proxy_set_header.

Le piège est l’héritage : selon le module proxy, les proxy_set_header sont hérités du niveau supérieur si et seulement si le niveau courant n’en définit aucun. Une location /ws/ qui ajoute Upgrade perd donc Host et les X-Forwarded-* : il faut les répéter.

Depuis nginx 1.29.7, proxy_http_version vaut 1.1 par défaut, ce qu’exigent les WebSocket : inutile de l’écrire avec la 1.30.6. nginx coupe après 60 secondes sans données : relevez proxy_read_timeout sur cette location, ou envoyez des trames ping. L’API du tutoriel n’a pas de WebSocket : ce bloc sert si vous en ajoutez.

En-têtes de sécurité

Ne doublez pas ce que l’application envoie déjà. Notre API utilise Helmet, qui pose par défaut Strict-Transport-Security: max-age=31536000; includeSubDomains, X-Content-Type-Options: nosniff et X-Frame-Options: SAMEORIGIN, entre autres (documentation de Helmet, consultée le 30 octobre 2026). Deux réglages suffisent côté nginx, avec un piège à connaître :

  • server_tokens off (contexte http) retire le numéro de version des pages d’erreur et de l’en-tête Server.
  • proxy_hide_header Strict-Transport-Security puis add_header ... always (bloc server du port 443) : nginx envoie un seul en-tête HSTS, y compris sur ses propres pages 502 ou 504. Sans always, add_header ne s’applique qu’à certains codes, dont 200, 301 et 302 (module headers).
  • L’héritage, encore. Un add_header placé dans une location annule ceux du server. Depuis nginx 1.29.3, add_header_inherit merge; cumule les deux niveaux, selon le même module headers.

Délais et taille des requêtes

Ces directives acceptent les contextes http, server et location ; nous les plaçons dans le server du port 443 :

  • client_max_body_size vaut 1 Mo par défaut, au-delà duquel nginx renvoie l’erreur 413. L’API refuse déjà tout JSON de plus de 10 ko : une limite de 16 ko côté nginx écarte les gros corps avant qu’ils n’atteignent Node.js. Relevez-la si vous ajoutez un envoi de fichiers.
  • proxy_connect_timeout vaut 60 secondes par défaut et ne peut généralement pas dépasser 75 secondes. Sur un réseau Docker local, 5 secondes suffisent pour échouer vite.
  • proxy_read_timeout vaut 60 secondes et mesure le silence entre deux lectures, pas la durée totale de la réponse.

Pour les menaces que ces réglages contrent, voyez notre point sur les cyberattaques les plus fréquentes.


Étape 5 : le fichier api.conf final

Voici nginx/conf.d/api.conf complet, qui remplace celui de l’étape 1. Les trois premières directives sont en contexte http ; tout le reste vit dans les deux blocs server.

# Contexte http (fichier inclus par nginx.conf)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

log_format proxy '$remote_addr [$time_local] "$request" $status '
                 'upstream=$upstream_status rt=$upstream_response_time '
                 'ct=$upstream_connect_time';

server_tokens off;

# Port 80 : défi Let's Encrypt, puis redirection vers HTTPS
server {
    listen 80;
    server_name api.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# Port 443 : le reverse proxy
server {
    listen 443 ssl;
    http2 on;
    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    access_log /var/log/nginx/access.log proxy;

    client_max_body_size  16k;
    proxy_connect_timeout 5s;
    proxy_read_timeout    60s;

    proxy_hide_header Strict-Transport-Security;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Hérités par toute location qui ne définit aucun proxy_set_header
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://api:3000;
    }

    location /ws/ {
        proxy_pass http://api:3000;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        $connection_upgrade;
        proxy_read_timeout 1h;
    }
}

Nous avons relu le contexte autorisé de chaque directive sur nginx.org : map et log_format n’acceptent que http, return accepte server et location, ssl_certificate et http2 acceptent http et server. Le paramètre http2 de listen est déprécié au profit de http2 on;, apparu dans nginx 1.25.1 (module HTTP/2), et ssl_protocols vaut déjà TLSv1.2 TLSv1.3 par défaut (module SSL).

Vérifiez, rechargez, puis testez :

docker compose exec nginx nginx -t && docker compose exec nginx nginx -s reload
curl -I http://api.example.com/health     # attendu : 301 vers https
curl -i https://api.example.com/health    # attendu : 200 et {"statut":"ok"}

Si nginx -t signale une erreur, le rechargement n’a pas lieu et l’ancienne configuration continue de tourner : selon le guide du débutant de nginx, le processus maître revient à l’ancienne configuration quand la nouvelle échoue.


Nginx Proxy Manager, Caddy ou Traefik ?

Le Nginx reverse proxy écrit à la main n’est pas la seule voie. Trois alternatives reviennent dans les recherches, avec des compromis différents.

Outil Configuration Certificats Point d’attention
nginx écrit à la main Fichiers texte, versionnés dans Git Certbot, à brancher soi-même Le plus de contrôle, le plus de travail
Nginx Proxy Manager Interface web sur le port 81 Let’s Encrypt intégré Configuration stockée en base, moins facile à relire dans Git
Caddy Fichier Caddyfile court Obtention et renouvellement automatiques par défaut Une syntaxe de plus à apprendre
Traefik Étiquettes sur les conteneurs Résolveur ACME, suivi des expirations automatique Accès à l’API Docker à protéger

Le guide de Nginx Proxy Manager utilise l’image jc21/nginx-proxy-manager:2.16.0. Caddy s’appuie sur Let’s Encrypt ou ZeroSSL et redirige vers HTTPS sans réglage. Traefik avertit qu’un accès sans restriction à l’API Docker pose un problème de sécurité.

Notre lecture : pour un projet ou deux, Caddy ou Nginx Proxy Manager font gagner du temps. Dès que la configuration doit être relue, versionnée et testée en CI, le fichier nginx explicite reste le plus prévisible.


Erreurs courantes et lecture des journaux

Chaque code d’erreur renvoyé par nginx pointe vers une cause précise, que les journaux nomment presque toujours. L’image officielle relie access.log à la sortie standard et error.log à la sortie d’erreur (dépôt docker-nginx) : docker compose logs --tail=50 nginx affiche les deux.

Symptôme Cause la plus fréquente Correctif
502 Bad Gateway L’API est arrêtée, écoute sur un autre port, ou son conteneur a changé d’adresse docker compose ps, vérifier le port 3000, recharger nginx
504 Gateway Timeout L’API ne répond pas dans le délai de proxy_read_timeout (60 s par défaut) Accélérer la route, ou relever le délai sur cette seule location
413 Request Entity Too Large Corps de requête au-dessus de client_max_body_size (16 ko dans notre fichier) Relever la limite dans le server ou la location concernée
Boucle de redirections derrière Cloudflare Mode SSL Flexible, et redirection HTTP vers HTTPS sur l’origine Passer en mode Full (strict)
Toutes les IP clientes identiques En-têtes absents, ou CDN devant nginx sans module realip Vérifier les quatre en-têtes, configurer set_real_ip_from

nginx résout le nom api au chargement de sa configuration : si le conteneur est recréé avec une nouvelle adresse, un nginx -s reload règle le 502. Pour s’en passer, nginx accepte depuis la 1.27.3 le paramètre resolve dans un bloc upstream doté d’une zone, avec une directive resolver (module upstream) ; le DNS intégré de Docker répond à l’adresse 127.0.0.11 (réseau Docker).

Derrière Cloudflare

La boucle de redirections est documentée par Cloudflare : en mode Flexible, Cloudflare contacte l’origine en HTTP, l’origine redirige vers HTTPS, et le cycle recommence. Le correctif conseillé est le mode Full ou Full (strict), qui exige un certificat sur l’origine, celui de Certbot convient (Cloudflare, trop de redirections, mis à jour le 1er septembre 2026).

Pour retrouver l’adresse réelle des visiteurs, Cloudflare recommande le module realip de nginx, avec ses plages d’adresses publiées sur cloudflare.com/ips (Cloudflare, adresses des visiteurs). Ces lignes vont dans le server du port 443 :

# Une ligne par plage publiée par Cloudflare, liste à tenir à jour
set_real_ip_from 192.0.2.0/24;
real_ip_header   CF-Connecting-IP;

Le module n’est pas compilé par défaut, mais les paquets de nginx.org, base de l’image officielle, l’incluent. $remote_addr devient l’adresse du visiteur, nginx l’ajoute en fin de X-Forwarded-For, et TRUST_PROXY=1 reste juste côté Express.

Lire les journaux utilement

Le format proxy du fichier final ajoute au journal d’accès le statut et les temps de l’API, grâce aux variables du module upstream. Un 502 accompagné, dans le journal d’erreurs, d’un échec de connexion vers api:3000 signale que nginx n’a pas joint l’API. Un rt= proche de 60 secondes avant un 504 désigne une route trop lente.

Pour filtrer ces lignes, nos commandes Linux de base couvrent grep et tail. Pour héberger la pile, voyez notre guide de l’hébergement cloud pour développeurs.


Ce que nous n’avons pas pu vérifier

Ce tutoriel est une synthèse de documentation, pas un retour d’expérience. Les points suivants restent à confirmer de votre côté :

  • L’exécution des fichiers. Ni Docker ni nginx n’étaient disponibles sur notre poste : nginx -t, docker compose up et Certbot n’ont pas été lancés.
  • Les droits du volume SQLite. La documentation de Docker dit qu’un volume vide reçoit le contenu du dossier de l’image ; la page lue ne précise pas que le propriétaire node de /app/data est conservé.
  • Le défi HTTP-01 derrière Cloudflare en mode proxy. Nous n’avons pas trouvé de page de Cloudflare qui le décrive pour ce cas précis. En cas d’échec, désactivez le proxy le temps de l’émission ou passez au défi DNS-01.
  • Les plages d’adresses de Cloudflare. Elles changent : 192.0.2.0/24 est une adresse de documentation, à remplacer par la liste officielle.

Questions fréquentes

Qu’est-ce qu’un Nginx reverse proxy ?

C’est un serveur nginx placé devant une ou plusieurs applications : il reçoit les requêtes des clients, termine HTTPS et les transmet au bon service avec proxy_pass. Selon MDN, un reverse proxy masque l’identité des serveurs, là où un proxy direct masque celle des clients.

Faut-il nginx stable ou mainline en 2026 ?

Les deux reçoivent les correctifs de sécurité : le 14 octobre 2026, nginx.org a publié la 1.30.6 stable et la 1.31.7 mainline avec les mêmes corrections. La branche stable change moins souvent, d’où notre choix de l’étiquette nginx:1.30.6-alpine.

Comment corriger une erreur 413 avec nginx ?

Relevez client_max_body_size, qui vaut 1 Mo par défaut selon la documentation de nginx, dans le bloc server ou dans la seule location qui reçoit des fichiers. Vérifiez ensuite la limite de l’application, qui peut être plus basse.

Les certificats Let’s Encrypt sont-ils toujours valables 90 jours ?

Oui pour l’instant avec le profil par défaut, classic. Le calendrier de Let’s Encrypt prévoit 64 jours à partir du 10 février 2027 et 45 jours à partir du 16 février 2028. Un certbot renew planifié chaque jour absorbe ce changement sans modification.


À propos de l’auteur

Abdelilah Ourti est ingénieur IA et architecte logiciel. Il conçoit et déploie des applications web et des systèmes d’IA, et publie sur NeurauTech des guides et des comparatifs destinés aux développeurs francophones. Ses autres articles sont réunis sur sa page auteur.

Méthode. Tutoriel fondé sur des sources publiques consultées le 30 octobre 2026 : nginx.org, Docker, Certbot, Node.js, Let’s Encrypt, Cloudflare, Express, Helmet, Nginx Proxy Manager, Caddy et Traefik. Le Dockerfile reprend celui de notre guide Docker. Aucune commande n’a été exécutée, Docker n’étant pas installé sur le poste de rédaction : la syntaxe nginx n’a pas été validée par nginx -t, seulement relue directive par directive contre la documentation de nginx.org. Les citations en anglais sont traduites par nos soins. Aucun lien n’est affilié et aucune relation commerciale ne lie l’auteur aux éditeurs cités.

Une erreur ou une version périmée ? Écrivez à [email protected] : l’article sera corrigé et la date de mise à jour modifiée.

Dernière mise à jour : 30 octobre 2026.

Image : Scott Rodgerson (Unsplash License).

Publications similaires