Web Server

Nginx

Nginx est le serveur web orienté événements qui se place devant presque toutes les applications Node, Python et Go en production. Il gère la terminaison TLS, sert des fichiers statiques, équilibre la charge et protège votre processus contre l'internet.

intermediate15 min readUpdated 16 sept. 2026
sites-available/app
nginx
# /etc/nginx/sites-available/app
server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

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

  location / {
    proxy_pass http://127.0.0.1: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;
  }
}
Sortie
2004
Modèle
Orienté événements, async
Rôles principaux
Serveur web, reverse proxy, load balancer
Langage de config
Blocs déclaratifs
TLS
Terminé au niveau de Nginx
Upstreams typiques
Node, Python, Go, PHP

Pourquoi c'est important

Ce que Nginx décharge de votre application

Un point d'entrée unique pour tout

Une seule adresse publique fait front pour plusieurs services. Routez par nom d'hôte ou par chemin vers différentes applications, et déplacez-les sans modifier le DNS ou le code client.

TLS et sécurité en périphérie

Nginx termine le HTTPS, redirige le HTTP simple, ajoute des en-têtes de sécurité et limite le débit du trafic abusif avant même qu'il n'atteigne votre processus.

Livraison statique rapide et mise en cache

Les fichiers statiques et les réponses mises en cache sont servis depuis le disque avec sendfile et le cache du noyau, ce qui est bien moins coûteux que de demander à Node de le faire.

Le tableau complet

Les trois rôles de Nginx

Il accepte les connexions provenant d'internet, décide quoi faire de chaque requête et gère le TLS pour que votre application n'ait pas à le faire.

L'écouteur (Listener)

Accepter

Nginx lie les ports, effectue le handshake TLS et lit la requête. Votre application ne voit jamais le socket brut.

Le routeur

Associer

server_name sélectionne l'hôte virtuel, puis les blocs location font correspondre le chemin et décident de servir, proxifier, mettre en cache ou rejeter.

L'upstream

Transférer

proxy_pass transmet la requête à votre application, ajoute des en-têtes de transfert et renvoie le flux de réponse au client.

Nginx en un coup d'œil

Les directives que vous utiliserez le plus

server blocks

Un par site ou nom d'hôte, sélectionné par server_name et le port.

location blocks

Correspondent à un préfixe de chemin ou une regex et choisissent comment le traiter.

proxy_pass

Transfère une requête vers une application upstream ou un autre serveur.

TLS

Les certificats, protocoles et paramètres de chiffrement résident dans le server block.

upstream

Un pool de backends avec une méthode d'équilibrage et des tests de santé (health checks).

limit_req & proxy_cache

Limitent le débit des utilisateurs abusifs et mettent en cache les réponses pour réduire la charge sur l'origine.

Flux

Le flux d'une requête à travers le reverse proxy

Chaque requête suit le même chemin, qu'elle se termine par un fichier statique ou par votre application.

  1. 1

    Le client se connecte

    Un navigateur ouvre une connexion TCP sur le port 443 de l'adresse publique du serveur et lance un handshake TLS.

  2. 2

    Nginx termine le TLS

    Nginx présente le certificat et déchiffre la requête, ainsi l'application upstream ne traite que du HTTP simple.

  3. 3

    L'hôte virtuel est choisi

    Nginx compare l'en-tête Host avec server_name pour choisir le bon server block pour ce domaine.

  4. 4

    Un emplacement est associé

    Le chemin de la requête est comparé aux blocs location dans l'ordre, et la correspondance la plus spécifique détermine le gestionnaire.

  5. 5

    La requête est servie ou proxifiée

    Nginx renvoie un fichier statique ou transfère la requête vers l'upstream défini par proxy_pass.

  6. 6

    Les en-têtes de transfert sont ajoutés

    Host, X-Real-IP, X-Forwarded-For et X-Forwarded-Proto informent l'application du client et du schéma réels.

  7. 7

    Le cache et les limites s'appliquent

    proxy_cache peut satisfaire la requête depuis le disque, et limit_req peut la rejeter, avant même que quoi que ce soit n'atteigne l'upstream.

  8. 8

    La réponse est renvoyée

    Nginx renvoie le flux de réponse de l'upstream au client, en le compressant éventuellement et en ajoutant des en-têtes en cours de route.

Le guide complet

Nginx: Tout ce que vous devez savoir

Qu’est-ce que Nginx concrètement

Nginx (prononcé “engine-x”) est un serveur web conçu autour d’une architecture asynchrone et pilotée par les événements (event-driven). Au lieu d’allouer un thread ou un processus par connexion, un petit nombre de processus “worker” gèrent simultanément des milliers de connexions en réagissant aux événements. C’est grâce à cette conception qu’il peut se placer devant une application lente sans s’effondrer, et c’est pourquoi il est devenu la porte d’entrée par défaut du web moderne.

Il remplit trois rôles que l’on a tendance à confondre. C’est un serveur web statique, capable de renvoyer efficacement des fichiers depuis le disque. C’est un reverse proxy, qui transfère les requêtes vers un autre serveur et relaie la réponse. Et c’est un load balancer, qui répartit les requêtes entre un pool de backends. Devant une application Node.js, Python, Go ou PHP, vous utiliserez ces trois fonctionnalités. Nginx ne fait pas partie de votre application : c’est un processus distinct avec sa propre configuration, son cycle de vie et ses propres logs.

Pourquoi le placer devant votre application

Vous pourriez lier Node directement au port 80. C’est ce que font beaucoup de tutoriels, et cela fonctionne… jusqu’au jour où ça ne fonctionne plus. Placer Nginx en amont vous apporte un ensemble de fonctionnalités fastidieuses ou dangereuses à implémenter directement dans une application.

Terminaison TLS. Nginx détient le certificat, effectue le handshake et déchiffre le flux. Votre application communique en HTTP simple sur localhost et n’a jamais à gérer le renouvellement des certificats. Certbot peut même réécrire la configuration pour vous.

Fichiers statiques, compression et mise en cache. Nginx sert les fichiers avec sendfile et le caching du noyau, compresse via gzip ou brotli, et peut renvoyer une réponse stockée sans même solliciter l’upstream.

Limitation du débit (Rate limiting) et limites de requêtes. Les abus sont stoppés dès l’entrée, avant même de consommer un pool de connexions ou une requête en base de données.

Un point d’entrée unique. Une seule adresse publique et un seul certificat peuvent servir de façade à plusieurs services, routés par hostname ou par chemin. Vous pouvez déplacer une application vers un autre port ou un autre hôte sans modifier le DNS.

Déploiements sans interruption (Zero-downtime). Un upstream peut vider un backend pendant qu’il redémarre, et le rechargement de la configuration se fait de manière fluide (graceful). À l’inverse, un Node exposé directement coupe toutes les connexions en cours lors d’un redémarrage.

Le modèle de configuration

La configuration de Nginx est un arbre de contextes et de directives. Une directive est un paramètre se terminant par un point-virgule ; un contexte est un bloc entouré d’accolades qui contient d’autres directives. Les directives sont héritées vers le bas, et c’est le contexte le plus spécifique qui l’emporte.

# /etc/nginx/nginx.conf (main context)
user  www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
  worker_connections 1024;
}

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  sendfile      on;
  keepalive_timeout 65;

  gzip on;
  gzip_types text/css application/javascript application/json;

  # site configs are pulled in here
  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

Les contextes dans lesquels vous travaillerez sont :

  • main — les paramètres globaux tels que user et worker_processes.
  • events — la manière dont les connexions sont gérées.
  • http — tout ce qui concerne HTTP : types MIME, journalisation, compression et les includes qui importent les fichiers du site.
  • server — un hôte virtuel, sélectionné par listen et server_name.
  • location — un chemin à l’intérieur d’un serveur, où les requêtes sont réellement traitées.

Sur Debian et Ubuntu, la convention consiste à garder un fichier par site dans /etc/nginx/sites-available/ et à créer des liens symboliques vers ceux qui sont activés dans /etc/nginx/sites-enabled/. Cela vous offre un moyen simple de désactiver un site sans le supprimer.

sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
sudo rm /etc/nginx/sites-enabled/app

Les directives ne constituent pas un langage de script. Il n’y a pas de boucles ni de variables au sens habituel — seulement map, if (utilisés avec parcimonie) et des includes. Si un problème semble nécessiter un flux de contrôle, la solution est généralement un map ou une modification de l’application.

Votre premier bloc server

Un bloc server est un hôte virtuel. Il écoute sur un port, répond à un ou plusieurs noms et contient des blocs location.

server {
  listen 80;
  server_name app.example.com www.app.example.com;

  root /var/www/app/dist;
  index index.html;

  location / {
    try_files $uri $uri/ =404;
  }
}

Le server_name est comparé à l’en-tête Host. Le premier bloc server pour un port est le serveur par défaut et intercepte les requêtes dont l’hôte ne correspond à rien d’autre ; il est donc judicieux d’en faire un “catch-all” explicite qui renvoie une erreur 444 ou une page de maintenance plutôt que de laisser fuiter un site non intentionnel.

L’ordre de correspondance est crucial et peut être source d’erreurs. Les blocs location sont testés d’abord comme correspondances exactes (=), puis via la correspondance de préfixe la plus longue, et enfin via des expressions régulières (~ et ~*). Un préfixe ^~ indique à Nginx de s’arrêter et d’utiliser ce préfixe sans vérifier les expressions régulières. Lorsque le comportement semble incorrect, c’est généralement à cause de cet ordonnancement.

location = /favicon.ico { log_not_found off; access_log off; }
location ^~ /assets/    { expires 1y; }
location ~* \.(jpg|png|css|js)$ { expires 30d; }
location /              { try_files $uri /index.html; }

Proxying vers une application Node.js

La directive principale est proxy_pass. Pointez-la vers votre application et Nginx devient un reverse proxy.

upstream app {
  server 127.0.0.1:3000;
  keepalive 32;
}

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

  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_read_timeout 30s;
    proxy_connect_timeout 5s;
  }
}

Définir un bloc upstream plutôt que d’intégrer l’adresse en ligne ne sert pas uniquement à gérer plusieurs serveurs. C’est ce qui vous permet de configurer keepalive, afin que Nginx réutilise les connexions vers le backend au lieu d’ouvrir une nouvelle connexion TCP pour chaque requête. proxy_http_version 1.1 est requis pour le keepalive et pour les WebSockets.

Les timeouts méritent une attention particulière. proxy_connect_timeout limite le temps pendant lequel Nginx attend d’établir la connexion ; proxy_read_timeout limite le temps d’attente pour le prochain octet provenant de l’upstream. Le timeout de lecture doit être supérieur à votre réponse légitime la plus lente, sinon Nginx retournera une erreur 504 alors que l’application travaille encore.

La règle du slash final est le piège classique. proxy_pass http://app; transfère le chemin complet sans modification. proxy_pass http://app/; remplace le préfixe de localisation correspondant par /. À l’intérieur de location /api/, le premier envoie /api/users et le second envoie /users. Choisissez délibérément et vérifiez les logs de l’upstream.

Transfert des headers et pourquoi votre application en a besoin

Par défaut, le serveur upstream voit la requête comme provenant de Nginx : l’adresse client est 127.0.0.1, le schéma est http, et le header Host peut être manquant. Cela rend dysfonctionnels les logs, les redirections, les cookies et la limitation de débit (rate limiting) au sein de l’application.

location / {
  proxy_pass http://app;
  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 X-Forwarded-Host  $host;
  proxy_set_header X-Forwarded-Port  $server_port;
}

Chaque header a son utilité :

  • Host préserve le domaine d’origine, permettant à l’application de construire des URLs absolues et des hôtes virtuels corrects.
  • X-Real-IP correspond à l’adresse immédiate du client.
  • X-Forwarded-For s’ajoute à une chaîne ; $proxy_add_x_forwarded_for conserve les entrées existantes et ajoute le client actuel. Utilisez la première entrée comme client d’origine, et ne lui faites jamais confiance aveuglément.
  • X-Forwarded-Proto indique à l’application si l’utilisateur s’est connecté via HTTPS, ce qui corrige les boucles de redirection et les bugs de cookies sécurisés.
  • X-Forwarded-Host et X-Forwarded-Port sont utiles lorsque Nginx écoute sur un port non standard.

Votre framework doit être configuré pour faire confiance à ce proxy. Dans Express, app.set("trust proxy", 1) permet à req.ip et req.protocol de lire les valeurs transférées. Si vous ignorez cette étape, chaque requête semblera provenir de localhost, ce qui cassera silencieusement la géolocalisation et la limitation de débit par IP.

Servir des fichiers statiques et le fallback SPA

Nginx est excellent pour la gestion des fichiers statiques, laissez-le donc s’en charger. Une répartition courante consiste à servir le frontend buildé depuis le disque et à ne proxier que l’API.

server {
  listen 80;
  server_name app.example.com;
  root /var/www/app/dist;

  location /assets/ {
    try_files $uri =404;
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  location /api/ {
    proxy_pass http://app;
    proxy_set_header Host $host;
  }

  location / {
    try_files $uri /index.html;
  }
}

Pour une application mono-page (SPA), try_files $uri /index.html est le fallback qui permet au routage côté client de fonctionner : si le chemin demandé n’est pas un fichier, on retourne index.html et on laisse le routeur gérer la suite. Pour une API, le même schéma avec =404 est préférable, car retourner du HTML pour un endpoint manquant masquerait des bugs.

Les assets dont le nom contient un hash de contenu peuvent être mis en cache indéfiniment — immutable et une expiration d’un an sont sans risque car toute modification génère un nouveau nom de fichier. index.html ne doit jamais être mis en cache de cette manière, sinon les utilisateurs continueront de charger une ancienne version de l’application.

TLS avec Let’s Encrypt et certbot

Certbot permet d’obtenir des certificats gratuits via Let’s Encrypt et peut configurer Nginx automatiquement. Installez le plugin, lancez-le une fois par domaine, et il s’occupe du reste.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com -d www.app.example.com
sudo certbot renew --dry-run

Certbot inscrit les chemins des certificats dans votre bloc serveur et installe un minuteur de renouvellement. Les certificats sont valables 90 jours ; le renouvellement est automatique, mais il ne fonctionne que si le test de renouvellement peut atteindre votre serveur sur le port 80, donc ne le bloquez pas entièrement via votre firewall.

Le bloc résultant ressemble à ceci, avec un serveur HTTP qui redirige et un serveur HTTPS qui termine la connexion.

server {
  listen 80;
  server_name app.example.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
  ssl_protocols       TLSv1.2 TLSv1.3;
  ssl_session_cache   shared:SSL:10m;
  ssl_session_timeout 1d;

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;

  location / {
    proxy_pass http://app;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

TLSv1.2 et TLSv1.3 sont les seuls protocoles qu’il vaille la peine d’activer aujourd’hui. HSTS indique aux navigateurs de refuser le HTTP simple pendant un an ; n’activez donc cette option que lorsque vous êtes certain que HTTPS fonctionne partout — car il est difficile de revenir en arrière.

HTTP/2 et HTTP/3

HTTP/2 multiplexe plusieurs requêtes sur une seule connexion et s’active au niveau du serveur. Sur les versions modernes de Nginx (1.25.1 et versions ultérieures), la directive est http2 on;. Sur les versions plus anciennes, il s’agit d’un flag sur la ligne listen : listen 443 ssl http2;.

server {
  listen 443 ssl;
  http2 on;
  # ...
}

HTTP/3 fonctionne via QUIC et UDP. Il nécessite une compilation avec le module QUIC, une ligne listen 443 quic reuseport; distincte et un header Alt-Svc: h3=":443"; ma=86400 pour l’annoncer. Considérez-le comme une optimisation à ajouter une fois que HTTP/2 est stable, et non comme une première étape. Les navigateurs négocient les deux via TLS automatiquement, il n’y a donc rien à modifier dans votre application.

Méthodes de répartition de charge (Load balancing)

Un bloc upstream définit un pool, et la méthode de répartition décide quel serveur reçoit la requête suivante. Le mode par défaut est le round-robin.

upstream app {
  least_conn;
  server 10.0.0.11:3000 weight=2 max_fails=3 fail_timeout=15s;
  server 10.0.0.12:3000;
  server 10.0.0.13:3000 backup;
  keepalive 64;
}
  • Round-robin (par défaut) cycle entre les serveurs. Simple et efficace lorsque les requêtes ont un coût approximativement identique.
  • least_conn envoie la requête au serveur ayant le moins de connexions actives. Préférable lorsque la durée des requêtes varie fortement, ce qui est courant pour les API.
  • ip_hash lie un client à un serveur spécifique en hachant l’adresse. Utile pour les sessions en mémoire, au prix d’une charge inégale et d’une perte de persistance si un serveur tombe.
  • weight oriente le trafic vers une machine plus puissante ou lors d’un déploiement canary.
  • backup désigne un serveur qui ne reçoit du trafic que lorsque les serveurs primaires sont indisponibles.

max_fails et fail_timeout permettent un contrôle de santé (health checking) passif : après max_fails échecs en l’espace de fail_timeout, Nginx marque le serveur comme indisponible pour cette période. proxy_next_upstream décide ensuite quels échecs doivent être retentés sur un autre backend.

proxy_next_upstream error timeout http_502 http_503 http_504;

Le mécanisme de retry est puissant mais n’est pas sans risque : il n’est pertinent que pour les requêtes idempotentes. Ne retentez pas aveuglément un POST qui a pu modifier l’état du système, à moins que l’upstream ne soit idempotent par conception.

Health checks et gestion des pannes

Nginx OSS propose des checks passifs ; les health checks actifs (health_check avec match) nécessitent Nginx Plus. Les checks passifs, combinés à un endpoint au niveau de l’application, suffisent à la plupart des équipes : exposez /healthz qui vérifie ses dépendances — base de données, cache — et répond rapidement, puis pointez la liveness probe de votre plateforme vers celui-ci. Utilisez-le pour sécuriser vos déploiements en démarrant un nouveau backend, en attendant qu’il soit sain (healthy), puis en supprimant l’ancien de l’upstream.

Comme les rechargements de configuration sont gracieux, le pattern pour un déploiement sans interruption (zero-downtime) consiste à modifier l’upstream pour pointer vers la nouvelle instance, nginx -t, recharger, puis vidanger (drain) et arrêter l’ancienne.

Mise en cache avec proxy_cache

proxy_cache stocke les réponses du serveur upstream sur le disque et les sert sans contacter le backend. Pour les points de terminaison avec beaucoup de lectures et qui évoluent lentement, c’est le gain de performance le plus important possible au niveau de l’edge.

proxy_cache_path /var/cache/nginx levels=1:2
                 keys_zone=app:10m max_size=1g inactive=60m;

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

  location /api/public/ {
    proxy_pass http://app;
    proxy_cache app;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    proxy_cache_valid 200 302 10s;
    proxy_cache_valid 404 1m;
    proxy_cache_bypass $http_authorization $cookie_session;
    proxy_no_cache $http_authorization;
    add_header X-Cache-Status $upstream_cache_status;
  }
}

proxy_cache_valid définit la durée de mise en cache pour chaque code de statut. proxy_cache_bypass ignore le cache pour les requêtes contenant un header de session ou d’autorisation ; proxy_no_cache empêche le stockage de leurs réponses, afin que les données privées d’un utilisateur ne soient jamais servies à un autre. Le header X-Cache-Status (HIT, MISS, BYPASS, EXPIRED) est le moyen le plus rapide de vérifier que la mise en cache fonctionne.

Deux mises en garde. Ne mettez en cache que les réponses qui peuvent être partagées — des données publiques et non personnalisées — et assurez-vous que le serveur upstream envoie un header Cache-Control compatible avec votre configuration. Une clé de cache qui ignore une variante (un header de langue, un paramètre de requête) servira sans hésiter le mauvais contenu.

Limitation du débit avec limit_req

La limitation du débit (rate limiting) protège les points de terminaison de connexion, la recherche et les API publiques contre les abus et les flux accidentels. Elle est définie dans le contexte http et appliquée dans une location.

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_status 429;

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

  location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://app;
  }

  location = /api/login {
    limit_req zone=login burst=5 nodelay;
    proxy_pass http://app;
  }
}

La clé de zone est généralement $binary_remote_addr (l’IP du client), et le débit est exprimé en requêtes par seconde ou par minute. burst permet de gérer de courts pics de trafic, et nodelay traite le burst immédiatement plutôt que de l’espacer — ce qui est préférable pour les points de terminaison interactifs. Sans nodelay, les requêtes excédentaires sont retardées, ce qui peut donner l’impression que l’interface utilisateur est lente au lieu de simplement retourner une erreur 429.

Derrière un autre proxy ou un CDN, $binary_remote_addr peut correspondre à l’adresse du proxy, ce qui amène le limiteur à traiter tous les utilisateurs comme un seul et même client. Corrigez cela en configurant set_real_ip_from et real_ip_header pour que Nginx voie le client réel. Retournez également 429 plutôt que le 503 par défaut afin que les clients puissent distinguer la limitation du débit d’une panne de service.

Mise à niveau WebSocket

Une connexion WebSocket commence par une requête HTTP avec un en-tête Upgrade, puis devient un flux bidirectionnel permanent. Pour assurer le proxying, les en-têtes de mise à niveau ainsi qu’une valeur Connection dépendant de la requête sont nécessaires, c’est pourquoi on utilise un map.

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  location /ws/ {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host       $host;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
  }
}

Le timeout de lecture prolongé est crucial : un WebSocket inactif avec un timeout court sera fermé par Nginx, et le client constatera une déconnexion mystérieuse. Notez que HTTP/2 ne transporte pas WebSocket de la même manière ; les navigateurs ouvrent une connexion HTTP/1.1 distincte pour la mise à niveau, qu’Nginx gère automatiquement sur le même port.

Exécuter Nginx dans Docker

L’image officielle nginx est un moyen pratique de livrer la configuration et les fichiers statiques avec votre application. La seule règle est que le processus principal du conteneur doit rester au premier plan, c’est pourquoi la commande se termine par daemon off;.

# Dockerfile
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/app.conf
COPY dist/ /usr/share/nginx/html/
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

Dans Compose, le service de l’application est accessible via son nom de service, donc l’hôte upstream est simplement app. Nginx démarre avant que l’application ne soit prête ; vous pouvez donc soit ajouter un healthcheck et en dépendre, soit laisser Nginx réessayer.

# docker-compose.yml
services:
  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/app.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      app:
        condition: service_healthy
  app:
    build: .
    expose:
      - "3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
      interval: 5s
      retries: 10

Notez l’utilisation de expose plutôt que ports sur l’application : celle-ci ne doit être accessible qu’à l’intérieur du réseau Compose, et jamais directement depuis l’hôte. L’upstream de la configuration Nginx devient alors server app:3000;.

Tests, rechargement et modifications de configuration sans interruption

Chaque modification de configuration doit suivre le même cycle : valider, recharger, vérifier. Un rechargement est gracieux ; un redémarrage ne l’est pas.

sudo nginx -t                 # test the configuration
sudo nginx -T                 # test and dump the full effective config
sudo systemctl reload nginx   # graceful, no dropped connections
sudo systemctl status nginx
curl -I https://app.example.com

nginx -T est sous-utilisé et extrêmement utile : il affiche la configuration fusionnée exactement telle que Nginx la voit, ce qui permet de trancher tout débat sur l’héritage et les inclusions. Si -t échoue, l’erreur indique le fichier et la ligne concernés.

Le rechargement permet de garder un shell ouvert dans une seconde session, afin qu’une mauvaise configuration ne vous bloque pas l’accès. Dans un conteneur, l’équivalent consiste à envoyer SIGHUP au processus maître ou à exécuter nginx -s reload. Ne redémarrez jamais un Nginx de production actif pour appliquer un changement de configuration lorsqu’un rechargement suffit.

Bonnes pratiques

  • Exécutez nginx -t avant chaque rechargement ; ne modifiez et ne redémarrez jamais à l’aveugle.
  • Conservez un fichier par site et activez-le via un lien symbolique afin que les modifications soient réversibles.
  • Définissez un bloc upstream avec keepalive au lieu d’intégrer l’adresse du backend en dur.
  • Transmettez toujours Host, X-Real-IP, X-Forwarded-For et X-Forwarded-Proto, et configurez trust proxy dans l’application.
  • Servez les assets statiques et les bundles hashés depuis le disque avec des headers de cache immuables et de longue durée.
  • Redirigez le HTTP vers le HTTPS et activez le HSTS uniquement après avoir validé le fonctionnement du HTTPS.
  • Mettez en cache uniquement les réponses publiques et non personnalisées, et ne créez jamais de clé de cache sans ses variantes.
  • Appliquez un rate limit sur la connexion et les endpoints publics, et retournez une erreur 429 plutôt qu’une 503.
  • Utilisez des timeouts longs pour les emplacements WebSocket et courts pour les health checks.
  • Ne stockez pas les secrets et les certificats dans les images ; montez-les en lecture seule au moment de l’exécution.

Erreurs courantes

  • Sauter l’étape nginx -t et faire tomber le site avec une erreur de syntaxe.
  • Oublier X-Forwarded-Proto, puis passer des heures à déboguer une boucle de redirection HTTPS infinie.
  • Ne pas activer trust proxy dans l’application, faisant ainsi croire que chaque requête provient de localhost.
  • Confondre proxy_pass avec ou sans slash final et se retrouver avec des erreurs 404.
  • Configurer proxy_read_timeout sur une durée plus courte qu’une réponse lente légitime et retourner des erreurs 504.
  • Mettre en cache des réponses personnalisées et servir les données d’un utilisateur à un autre.
  • Proxyer des WebSockets sans le map d’upgrade, et voir les connexions couper immédiatement.
  • Laisser le serveur par défaut comme premier bloc de site et exposer une application non souhaitée.
  • Activer HSTS avant que HTTPS ne fonctionne partout et bloquer l’accès aux utilisateurs pendant un an.
  • Exposer le service de l’application avec ports dans Compose au lieu de expose.

Et après ?

Nginx est votre porte d’entrée, et le guide HTTP / HTTPS explique le protocole qu’il termine et redirige. Pour l’exécuter à côté de votre application de manière reproductible, consultez Docker & Deployment, et pour maintenir la santé de l’hôte sur lequel il tourne, revenez à la section Linux. Lorsque vous serez prêt à le déployer sur l’internet public avec des certificats, le DNS et l’autoscaling, le guide Cloud Deployment vous aidera à assembler toutes ces pièces.

En pratique

Les quatre configurations que vous écrirez le plus

Commencez par un proxy, ajoutez le TLS, passez à un pool, puis ajoutez la mise en cache et la limitation du débit.

sites-available/app
upstream app {
  server 127.0.0.1:3000;
  keepalive 32;
}

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

  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    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_read_timeout 30s;
  }
}

Reverse proxy vs exposition directe de l'app

Exécuter Node sur le port 80 signifie renoncer au TLS, au cache statique, à la limitation du débit et à la possibilité de déployer sans interruption. Nginx ne coûte qu'un processus et apporte tout cela.

Préférer
server {
  listen 443 ssl;
  server_name app.example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
  }
}
Éviter
# Node listening on :80 directly
# - no TLS termination
# - no static file cache
# - no rate limiting
# - restart drops connections
# - one process owns the port

ip_hash vs round-robin

Le round-robin répartit la charge uniformément. ip_hash lie un client à un seul backend, ce qui est utile pour les sessions persistantes (sticky sessions) mais crée une charge inégale et perd ce lien si un serveur tombe.

Charge équilibrée
upstream app {
  least_conn;
  server 10.0.0.11:3000;
  server 10.0.0.12:3000;
}
# Stateless apps and shared
# session stores scale cleanly.
Sessions persistantes
upstream app {
  ip_hash;
  server 10.0.0.11:3000;
  server 10.0.0.12:3000;
}
# Pins clients, but a restart
# reshuffles their sessions.

Compromis

Nginx vaut-il le processus supplémentaire ?

Nginx ajoute un langage de configuration et un deuxième élément à déployer. Pour tout service public, les fonctionnalités de périphérie rentabilisent rapidement ce coût.

Strengths

  • Sécurité en périphérie

    Le TLS, les en-têtes de sécurité, les limites de taille de requête et la limitation du débit sont appliqués avant qu'une requête n'atteigne le code de l'application, réduisant ainsi l'exposition des bugs.

  • Performance gratuite

    Les fichiers statiques, le gzip et les réponses mises en cache sont servis par un serveur C orienté événements, libérant le thread unique de Node pour le travail réel.

  • Déploiements sans interruption

    Le rechargement de Nginx est gracieux, et un upstream peut vider un backend pendant qu'il redémarre, afin que les utilisateurs ne voient jamais de connexion refusée.

Trade-offs

  • Un autre outil à apprendre et à opérer

    La syntaxe de config, les règles d'ordre et la sémantique de rechargement représentent une surface d'apprentissage réelle. Une faute de frappe peut faire tomber un site si vous oubliez nginx -t.

  • La terminaison TLS déplace la frontière

    Le trafic entre Nginx et l'upstream est en HTTP simple. Sur un hôte ou un réseau partagé, chiffrez ou isolez également ce saut.

  • Il peut masquer des problèmes d'application

    La mise en cache et le buffering masquent les origines lentes, et les en-têtes de transfert sont faciles à oublier, ce qui conduit à des IP clients erronées et des redirections cassées.

FAQ

Foire aux questions

Keep learning

Related topics from the roadmap.

$ commencer à apprendre

Prêt à apprendre Nginx / Reverse Proxy ?

Notre tutoriel interactif vous guide à travers Nginx / Reverse Proxy pas à pas — avec des quiz et du vrai code que vous pouvez exécuter dans le navigateur.