Aller au contenu
medium

Nginx : guide pratique (reverse proxy, TLS, performances)

38 min de lecture

Logo Nginx

Nginx est un serveur web et reverse proxy ultra-performant. Si vous devez servir un site statique, proxyfier une API, activer HTTPS ou diagnostiquer une erreur 502, ce guide vous donne les recettes essentielles en quelques minutes.

Niveau : Débutant → Intermédiaire · Durée : 30-45 min

À la fin de ce guide, vous saurez :

  • Servir un site statique en 5 minutes
  • Configurer un reverse proxy vers une application
  • Activer HTTPS avec Let's Encrypt (TLS 1.2/1.3)
  • Diagnostiquer les erreurs courantes (502, 504, 403)
  • Sécuriser et optimiser votre configuration

Les six entrées ci-dessous mènent chacune à une section autonome. Un serveur monté de zéro suit l'ordre site statique, reverse proxy, puis HTTPS. Une installation déjà en place se consulte par symptôme : partez du 502 ou du 403 que vous observez, le diagnostic renvoie ensuite vers la directive en cause.

Nginx (prononcé "engine-x") est à la fois :

  • Un serveur web pour fichiers statiques (HTML, CSS, JS, images)
  • Un reverse proxy pour transmettre les requêtes à des applications backend
  • Un load balancer pour répartir le trafic entre plusieurs serveurs
  • Un cache pour accélérer les réponses

Les paquets fournis par les distributions retardent souvent de plusieurs versions mineures. Les commandes ci-dessous déclarent le dépôt officiel nginx.org, dont la clé de signature est vérifiée par le gestionnaire de paquets, ce qui donne accès à la branche stable courante. Le service démarre avec une page d'accueil par défaut : elle sert à confirmer l'écoute sur le port 80 avant d'écrire la moindre configuration de site.

Fenêtre de terminal
# Ajouter le dépôt officiel Nginx (version récente)
sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
# Installer
sudo apt update && sudo apt install nginx
# Démarrer et activer au boot
sudo systemctl enable --now nginx

Vérification : ouvrez http://votre-ip dans un navigateur, vous devez voir la page "Welcome to nginx".

Fenêtre de terminal
# Vérifier que Nginx tourne
systemctl status nginx
# active (running) ✓
# Vérifier la version
nginx -v
# nginx version: nginx/1.26.x

C'est le cas d'usage le plus simple : afficher des fichiers HTML/CSS/JS.

  1. Créer le répertoire et le fichier

    Fenêtre de terminal
    sudo mkdir -p /var/www/monsite
    echo '<h1>Hello Nginx!</h1>' | sudo tee /var/www/monsite/index.html
  2. Créer la configuration du site

    Fenêtre de terminal
    sudo tee /etc/nginx/conf.d/monsite.conf << 'EOF'
    server {
    listen 80;
    server_name monsite.local;
    root /var/www/monsite;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    }
    EOF
  3. Tester et recharger la configuration

    Fenêtre de terminal
    # Toujours tester AVANT de recharger
    sudo nginx -t
    # nginx: configuration file /etc/nginx/nginx.conf test is successful
    sudo systemctl reload nginx
  4. Vérifier

    Fenêtre de terminal
    curl -I http://localhost
    # HTTP/1.1 200 OK

Le reverse proxy place Nginx devant une application qui écoute sur un port non privilégié. Nginx termine le TLS, applique le cache et les limites de débit, puis relaie la requête accompagnée des en-têtes X-Forwarded-* qui décrivent le client d'origine. Cette partie couvre la configuration de base, l'ajustement des timeouts pour les traitements longs, et le cas particulier des WebSockets qui exigent un changement de protocole.

Un reverse proxy est un intermédiaire entre vos utilisateurs et votre application. Sans lui, votre app serait exposée directement sur Internet.

Scénario concret : Vous avez une API Node.js qui tourne sur localhost:3000. Vous voulez :

  1. Qu'elle soit accessible sur api.example.com (pas example.com:3000)
  2. Qu'elle utilise HTTPS (votre app Node ne gère pas TLS)
  3. Que les logs HTTP soient centralisés
  4. Pouvoir ajouter du rate limiting, du cache, etc.

Le reverse proxy fait tout ça. Votre app reste simple, Nginx gère la complexité HTTP.

QuestionRéponse
"Pourquoi pas exposer mon app directement ?"Sécurité (TLS), flexibilité (plusieurs apps), performances (cache)
"Pourquoi Nginx et pas mon framework ?"Nginx est optimisé pour ça (C10K), votre app pour la logique métier
"Quand est-ce inutile ?"Développement local, ou si vous utilisez un PaaS (Heroku, Vercel)

Sans les quatre directives proxy_set_header, le backend voit toutes les requêtes arriver de 127.0.0.1 en HTTP : les journaux applicatifs deviennent inexploitables et les redirections que l'application génère repartent en clair. La variable $proxy_add_x_forwarded_for complète la chaîne existante au lieu de l'écraser, ce qui compte dès qu'un CDN ou un load balancer se trouve en amont.

server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
# Headers essentiels pour que le backend connaisse le client réel
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;
}
}

Si votre backend met du temps à répondre (traitement lourd, IA, etc.) :

location / {
proxy_pass http://backend:3000;
# Augmenter les timeouts (défaut: 60s)
proxy_connect_timeout 120s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

Pour les applications temps réel (chat, notifications) :

location /ws/ {
proxy_pass http://backend:3000;
# Upgrade de connexion pour WebSocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

Nginx n'obtient pas les certificats lui-même : Certbot dialogue avec Let's Encrypt, dépose les fichiers dans /etc/letsencrypt et modifie les blocs server pour les référencer. Le certificat vaut 90 jours et un timer systemd déclenche le renouvellement bien avant l'échéance. Reste à votre charge le choix des protocoles TLS acceptés, que les valeurs par défaut ne garantissent pas selon la version de Nginx et d'OpenSSL installée.

HTTPS n'est plus une option, c'est une obligation :

Sans HTTPSAvec HTTPS
Les mots de passe transitent en clairTout est chiffré
Google pénalise votre SEOBonus de classement
Les navigateurs affichent "Non sécurisé"Cadenas vert
Vulnérable aux attaques man-in-the-middleCommunication sécurisée

Let's Encrypt est une autorité de certification gratuite. Certbot est l'outil qui automatise l'obtention et le renouvellement des certificats.

  1. Installer Certbot

    Fenêtre de terminal
    sudo apt install certbot python3-certbot-nginx
  2. Obtenir le certificat

    Fenêtre de terminal
    sudo certbot --nginx -d example.com -d www.example.com

    Certbot modifie automatiquement votre configuration Nginx.

  3. Vérifier la configuration générée

    Certbot crée une configuration similaire à :

    server {
    listen 443 ssl http2;
    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;
    # Forcer TLS 1.2/1.3 (ne pas se fier aux défauts)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    # HSTS : force HTTPS pendant 1 an
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    location / {
    root /var/www/html;
    index index.html;
    }
    }
    # Redirection HTTP → HTTPS
    server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
    }
  4. Configurer le renouvellement automatique

    Fenêtre de terminal
    # Tester le renouvellement
    sudo certbot renew --dry-run
    # Le timer systemd gère le renouvellement automatique
    systemctl list-timers | grep certbot

La configuration Nginx est organisée en plusieurs fichiers :

/etc/nginx/
├── nginx.conf # Config principale (global)
├── conf.d/ # Vos sites (1 fichier par site)
│ ├── default.conf
│ └── monsite.conf
├── mime.types # Types MIME
└── modules-enabled/ # Modules dynamiques (Debian)

Ce fichier ne décrit aucun site : il fixe le contexte dans lequel tous les blocs server s'exécuteront. Deux directives dimensionnent le serveur, worker_processes et worker_connections, dont le produit borne le nombre de connexions simultanées. server_tokens off retire le numéro de version des réponses et de la page d'erreur. La ligne include finale est ce qui rend actifs les fichiers déposés dans conf.d/.

user www-data; # Utilisateur des workers
worker_processes auto; # 1 worker par CPU
pid /run/nginx.pid;
events {
worker_connections 1024; # Connexions simultanées par worker
}
http {
# Performances
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# Types MIME
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logs
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# Masquer la version de Nginx
server_tokens off;
# Inclure les configurations des sites
include /etc/nginx/conf.d/*.conf;
}

Le dépannage Nginx suit toujours le même schéma : est-ce que le problème vient de la config ? Du backend ? Des permissions ? Des logs ?

Avec les bonnes commandes, vous identifiez la cause en 30 secondes.

Pour les erreurs côté certificat (chaîne incomplète, mauvais SNI, expiration), appuyez-vous sur le guide diagnostic TLS avec openssl.

Toujours tester la configuration avant de recharger :

Fenêtre de terminal
sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

Si erreur, Nginx indique la ligne exacte du problème.

L'ordre de ces commandes remonte du processus vers la requête, ce qui évite de chercher une erreur de configuration alors que le service est arrêté. nginx -T se distingue de nginx -t en affichant la configuration effectivement chargée, tous les include résolus : c'est ce qui révèle un bloc server en doublon ou un fichier oublié dans conf.d/.

Fenêtre de terminal
# 1. Nginx tourne-t-il ?
systemctl status nginx
# 2. La config est-elle valide ?
sudo nginx -t
# 3. Dump complet de la config (includes résolus)
sudo nginx -T | less
# 4. Logs récents (systemd)
journalctl -u nginx --since "10 min ago"
# 5. Logs d'erreur Nginx
sudo tail -20 /var/log/nginx/error.log
# 6. Le port est-il écouté ?
ss -tlnp | grep :80

La distinction utile passe entre les erreurs que Nginx produit lui-même et celles qu'il subit. Les codes 403 et 404 viennent du système de fichiers local, donc des permissions ou de la directive root. Les codes 502 et 504 viennent du backend, et aucune modification de la configuration Nginx ne les corrigera si l'application ne répond pas.

ErreurCause probableSolution
403 ForbiddenPermissions fichierssudo chown -R www-data:www-data /var/www/
404 Not FoundMauvais root ou fichier absentVérifier le chemin dans la config
502 Bad GatewayBackend ne répond pas, ou SELinux bloque la connexion sortanteLire l'errno dans error.log : il départage les deux cas (voir ci-dessous)
504 Gateway TimeoutBackend trop lentAugmenter proxy_read_timeout
"Address already in use"Port déjà utiliséss -tlnp | grep :80 pour identifier

Rejouer la requête sous le compte www-data est l'étape qui tranche : si curl réussit en root et échoue sous le compte de service, le problème n'est pas l'application mais les droits ou SELinux.

Ce qui départage les deux causes, c'est le numéro d'errno entre parenthèses dans error.log. Une même 502 en recouvre deux, radicalement différentes :

Ligne dans error.logCause réelleCorrection
connect() … failed (111: Connection refused)Le backend n'écoute pasDémarrer l'application, vérifier son port
connect() … failed (13: Permission denied)SELinux bloque la connexion sortante de nginxsudo setsebool -P httpd_can_network_connect on

Le 13 est le piège : en Enforcing, nginx n'a pas le droit d'ouvrir une connexion réseau tant que le booléen httpd_can_network_connect n'est pas posé, et rien dans la page d'erreur ne le laisse deviner. Ce couple d'errno est tout le diagnostic.

Fenêtre de terminal
# 1. Le backend tourne-t-il ?
curl -I http://127.0.0.1:3000
# Si "Connection refused" → le backend est down
# 2. Nginx peut-il joindre le backend ?
sudo -u www-data curl http://127.0.0.1:3000
# Parfois c'est un problème de permissions/SELinux
# 3. Logs détaillés
sudo tail -f /var/log/nginx/error.log
# "connect() failed (111: Connection refused)"

Trois leviers indépendants agissent sur le temps de réponse perçu. La compression gzip réduit le volume transféré sur les contenus texte, le cache proxy évite de solliciter le backend pour une réponse déjà servie, et le load balancing répartit ce qui reste entre plusieurs instances. Ils se cumulent, mais le cache introduit un décalage entre le contenu réel et le contenu servi qu'il faut assumer et surveiller.

Réduit la taille des réponses de 60-80% :

http {
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
application/json
application/javascript
application/xml
image/svg+xml;
}

Stocke les réponses du backend pour réduire sa charge :

# Définir la zone de cache (dans http {})
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
server {
location / {
proxy_pass http://backend:3000;
proxy_cache my_cache;
proxy_cache_valid 200 10m; # Cache les 200 pendant 10 min
proxy_cache_valid 404 1m; # Cache les 404 pendant 1 min
# Header pour debug
add_header X-Cache-Status $upstream_cache_status;
}
}

Répartir le trafic entre plusieurs backends :

upstream backend_pool {
# Round-robin par défaut
server backend1.example.com:3000;
server backend2.example.com:3000;
server backend3.example.com:3000 backup; # Utilisé si les autres sont down
}
server {
location / {
proxy_pass http://backend_pool;
proxy_set_header Host $host;
}
}

Algorithmes disponibles :

upstream backend_pool {
least_conn; # Envoie au serveur le moins chargé
# ou
ip_hash; # Même client → même serveur (sessions)
server backend1:3000;
server backend2:3000;
}

Un serveur exposé sur Internet reçoit du trafic automatisé dès ses premières heures de vie. Les quatre mesures qui suivent couvrent des risques distincts : les en-têtes de sécurité contraignent le comportement du navigateur, le rate limiting absorbe les rafales de requêtes, les listes allow/deny ferment les chemins d'administration, et limit_except restreint les méthodes HTTP acceptées.

Le mot-clé always compte autant que l'en-tête lui-même : sans lui, Nginx omet l'en-tête sur les réponses d'erreur, précisément celles qu'un attaquant cherche à provoquer. Second point non intuitif, l'héritage n'est pas cumulatif : un seul add_header déclaré dans un bloc location annule tous ceux du bloc server parent, qu'il faut alors répéter.

server {
# Empêche le clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Empêche le sniffing MIME
add_header X-Content-Type-Options "nosniff" always;
# Politique de référent
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HSTS (après avoir vérifié que HTTPS fonctionne)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# CSP basique (à adapter selon votre app)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;
}

Protéger contre les abus et attaques DDoS légères :

# Définir la zone de limite (dans http {})
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
# Max 10 req/s, burst de 20 avec délai
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend:3000;
}
}

Les directives allow et deny sont évaluées dans l'ordre d'écriture et la première correspondance s'applique, d'où le deny all placé en dernier. Elles portent sur l'adresse que Nginx voit réellement : derrière un load balancer ou un CDN, cette adresse est celle de l'équipement amont, et le filtrage laisse alors passer tout le monde.

location /admin/ {
# Autoriser uniquement certaines IP
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://backend:3000;
}

limit_except se lit à l'envers de ce que son nom suggère : les méthodes listées sont celles qui échappent aux règles du bloc, toutes les autres reçoivent le deny all. Mentionner GET autorise implicitement HEAD, il est donc inutile de l'ajouter. Cette restriction bloque notamment les requêtes PUT et DELETE émises contre un serveur de fichiers statiques.

location / {
# Autoriser seulement GET, POST, HEAD
limit_except GET POST HEAD {
deny all;
}
root /var/www/html;
}

La version open source n'expose qu'un jeu de compteurs minimal, via le module stub_status : connexions actives, requêtes acceptées et traitées, répartition entre lecture, écriture et attente. Ces valeurs alimentent sans difficulté un exporter Prometheus et suffisent à suivre la charge, mais elles ne détaillent ni les codes de réponse ni les temps par route, informations qui restent à extraire des journaux d'accès.

Le module stub_status est compilé dans les paquets officiels, il n'y a rien à installer. L'endpoint doit rester limité à la boucle locale : il expose le volume de trafic du serveur et n'a aucune raison d'être joignable depuis l'extérieur, l'exporter tournant lui-même sur la machine ou sur le même hôte Docker.

server {
location /nginx_status {
stub_status on;
allow 127.0.0.1;
deny all;
}
}
Fenêtre de terminal
curl http://127.0.0.1/nginx_status
# Active connections: 42
# server accepts handled requests
# 1234567 1234567 9876543
# Reading: 0 Writing: 1 Waiting: 41

Pour un monitoring complet, utilisez nginx-prometheus-exporter :

Fenêtre de terminal
# Option 1 : --network host (Linux)
docker run --network host nginx/nginx-prometheus-exporter:latest \
-nginx.scrape-uri=http://127.0.0.1/nginx_status
# Option 2 : IP explicite du host
docker run -p 9113:9113 nginx/nginx-prometheus-exporter:latest \
-nginx.scrape-uri=http://192.168.1.10/nginx_status

Puis configurez Prometheus pour scraper localhost:9113/metrics et visualisez dans Grafana.

Les cinq questions ci-dessous portent sur les points où Nginx se comporte autrement qu'attendu : l'ordre d'évaluation des blocs location, la différence entre root et alias, les échecs de proxy_pass, le rechargement sans coupure et l'hébergement de plusieurs sites sur une même machine.

Pourquoi ma location regex ne matche pas ?

Nginx évalue les location dans un ordre précis :

  1. = (exact) → priorité absolue
  2. ^~ (préfixe prioritaire) → stoppe la recherche regex
  3. ~ / ~* (regex) → première regex qui matche
  4. préfixe simple → le plus long gagne
location = /api { } # Seulement /api exact
location ^~ /static/ { } # /static/* sans chercher regex
location ~ \.php$ { } # Regex : fichiers .php
location / { } # Catch-all

Piège courant : une location préfixe / capture tout avant que votre regex ne soit évaluée. Utilisez ^~ si vous voulez bloquer les regex sur un chemin.

Quelle est la différence entre root et alias ?
  • root : le chemin de l'URI est ajouté au root
  • alias : le chemin de l'URI est remplacé par l'alias
# Avec root : /images/photo.jpg → /var/www/images/photo.jpg
location /images/ {
root /var/www;
}
# Avec alias : /images/photo.jpg → /data/photos/photo.jpg
location /images/ {
alias /data/photos/;
}

Règle simple : utilisez root par défaut, alias seulement si vous voulez mapper une URL vers un chemin complètement différent.

Pourquoi mon proxy_pass ne fonctionne pas ?

Vérifiez dans l'ordre :

  1. Le backend tourne-t-il ?curl http://127.0.0.1:3000
  2. Le slash final → avec ou sans / change le comportement
  3. SELinux (RHEL) → setsebool -P httpd_can_network_connect 1
  4. Firewall → le port est-il ouvert ?
  5. Logstail /var/log/nginx/error.log
Comment recharger Nginx sans interruption ?
Fenêtre de terminal
# Tester d'abord
sudo nginx -t
# Recharger (pas de downtime)
sudo systemctl reload nginx
# OU
sudo nginx -s reload

Ne jamais utiliser restart en production si reload suffit.

Comment servir plusieurs sites sur le même serveur ?

Créez un fichier par site dans /etc/nginx/conf.d/ :

/etc/nginx/conf.d/site1.conf
server {
listen 80;
server_name site1.com;
root /var/www/site1;
}
# /etc/nginx/conf.d/site2.conf
server {
listen 80;
server_name site2.com;
root /var/www/site2;
}

Nginx utilise le server_name pour router les requêtes.

Chaque bloc ci-dessous se colle dans un fichier de conf.d/, en adaptant domaine et chemins. Un point de vigilance sur le contexte : les extraits qui ne montrent qu'un location s'insèrent dans un bloc server existant, tandis que les déclarations de zone comme proxy_cache_path et limit_req_zone se placent au niveau http, dans nginx.conf, sinon la configuration est rejetée au test.

Le bloc est autonome : il déclare son écoute, son domaine et sa racine. try_files renvoie un 404 explicite quand ni le fichier ni le répertoire demandés n'existent.

server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}

Le fallback vers /index.html remplace le =404 : toute route absente du disque est confiée au routeur JavaScript de l'application.

location / {
root /var/www/app;
try_files $uri $uri/ /index.html;
}

Ces trois proxy_set_header sont le minimum pour que le backend journalise l'adresse réelle du client et non celle de Nginx.

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_http_version 1.1 est obligatoire : en HTTP/1.0, l'en-tête Upgrade n'est pas transmis et la connexion retombe en HTTP classique.

location /ws/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

proxy_cache_path se déclare dans le contexte http, jamais dans un location. proxy_cache_valid fixe ensuite la durée de conservation par code de réponse.

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g;
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 10m;
}

burst=20 autorise une rafale de vingt requêtes au-delà du débit fixé, et nodelay les sert immédiatement au lieu de les étaler dans le temps.

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}

Le fichier .htpasswd se génère avec la commande htpasswd ; il doit être lisible par le worker Nginx et rester hors de la racine web.

location /admin/ {
auth_basic "Zone Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}

La première règle correspondante l'emporte : c'est le deny all final qui rend les allow effectifs.

location /admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://backend;
}

La variable $request_uri contient déjà le chemin et la chaîne de requête, ce qui préserve l'URL complète lors du 301.

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

Créer ou supprimer le fichier bascule le site sans recharger Nginx. La directive error_page intercepte le 503 et sert la page statique locale à la place du backend.

location / {
if (-f /var/www/maintenance.html) {
return 503;
}
proxy_pass http://backend;
}
error_page 503 /maintenance.html;
location = /maintenance.html {
root /var/www;
}

client_max_body_size vaut 1 Mo par défaut : au-delà, Nginx répond 413 sans même contacter le backend.

location /upload/ {
client_max_body_size 100M;
proxy_pass http://backend;
proxy_read_timeout 300s;
}

least_conn dirige la requête vers le backend ayant le moins de connexions actives, et le mot-clé backup réserve le troisième serveur aux périodes d'indisponibilité des deux autres.

upstream backend_pool {
least_conn;
server backend1:3000;
server backend2:3000;
server backend3:3000 backup;
}
location / {
proxy_pass http://backend_pool;
}

Ces huit erreurs partagent une caractéristique : la configuration est syntaxiquement valide, nginx -t ne signale rien, et le problème n'apparaît qu'à l'exécution. Les deux plus coûteuses en production sont le slash final de proxy_pass, qui modifie l'URI transmise sans le moindre message, et la résolution DNS figée en environnement conteneurisé, qui ne se manifeste qu'après un redéploiement du backend.

ErreurConséquenceSolution
Slash final dans proxy_pass/api/users → backend reçoit /users au lieu de /api/usersSans slash = URI conservée. Testez avec curl -v
Confondre root et alias404 car le chemin n'existe pasalias /data/photos/; avec slash final
Ordre des locationsUne location préfixe capture avant la regex= (exact) > ^~ (préfixe prio) > regex > préfixe
DNS statique en Docker/K8sNginx garde l'ancienne IP après redéploiementresolver 127.0.0.11; set $backend "http://app:3000"; proxy_pass $backend;
SELinux bloque le proxy502 Bad Gateway sur RHEL/Rockysetsebool -P httpd_can_network_connect 1
Permissions fichiers403 Forbidden sur les fichiers statiqueschown -R www-data:www-data /var/www && chmod -R 755 /var/www
try_files sans fallback SPA404 sur les routes frontendtry_files $uri $uri/ /index.html;
Logs dans le mauvais fichierDebug impossibleaccess_log /var/log/nginx/monsite-access.log;

Ces tableaux regroupent les directives par famille pour retrouver une syntaxe sans rouvrir la documentation. Un rappel qui évite bien des tests ratés : chaque directive n'est valide que dans certains contextes, http, server ou location, et Nginx refuse de démarrer si elle est placée ailleurs.

Ces blocs s'imbriquent dans un ordre imposé : location n'existe qu'à l'intérieur d'un server, et upstream uniquement au niveau http.

DirectiveDescription
server { }Bloc de configuration d'un site
location /path { }Bloc pour un chemin spécifique
upstream name { }Groupe de backends
include /path/*.conf;Inclure d'autres fichiers

Le couple listen et server_name décide quel bloc traite la requête. Si aucun nom ne correspond, Nginx retient le premier bloc déclaré sur ce port.

DirectiveDescription
listen 80;Écouter sur port 80
listen 443 ssl http2;HTTPS + HTTP/2
server_name example.com;Nom de domaine du site
server_name _;Catch-all (default server)

root ajoute l'URI au chemin indiqué, alias le remplace intégralement : c'est l'origine classique des 404 sur un fichier pourtant présent.

DirectiveDescription
root /var/www/html;Racine des fichiers
index index.html;Fichier par défaut
try_files $uri $uri/ =404;Chercher fichier/dossier ou 404
alias /autre/chemin/;Remplacer le chemin (avec slash)

Ces directives se placent dans un location. Les proxy_set_header déclarés localement remplacent en bloc ceux hérités du niveau parent, ils doivent donc être répétés.

DirectiveDescription
proxy_pass http://backend:3000;Transmettre au backend
proxy_set_header Host $host;Transmettre le Host original
proxy_set_header X-Real-IP $remote_addr;Transmettre l'IP client
proxy_read_timeout 120s;Timeout lecture backend

fullchain.pem contient le certificat et la chaîne intermédiaire ; charger cert.pem seul provoque des erreurs de validation sur les clients qui ne complètent pas la chaîne.

DirectiveDescription
ssl_certificate /path/fullchain.pem;Certificat + chaîne
ssl_certificate_key /path/privkey.pem;Clé privée
ssl_protocols TLSv1.2 TLSv1.3;Forcer TLS moderne
add_header Strict-Transport-Security "max-age=31536000";HSTS

nginx -s reload relit la configuration sans couper les connexions en cours et conserve l'ancienne version si la nouvelle est invalide, d'où l'intérêt du nginx -t préalable.

CommandeDescription
nginx -tTester la configuration
nginx -TDump config complète (includes résolus)
nginx -s reloadRecharger sans interruption
nginx -s stopArrêter immédiatement

Ces sept points recouvrent les réflexes qui évitent la majorité des interruptions de service sur un Nginx en production, du test de configuration au comportement des en-têtes transmis au backend.

  1. nginx -t : toujours tester avant de recharger
  2. server {} + location {} : les 2 blocs fondamentaux
  3. TLS 1.2/1.3 uniquement : jamais TLS 1.0/1.1
  4. proxy_set_header : transmettre les infos client au backend
  5. Slash dans proxy_pass : avec ou sans change tout
  6. reloadrestart : reload = sans interruption
  7. Logs : /var/log/nginx/error.log est votre ami

Nginx couvre le service de fichiers et le reverse proxy sur une machine donnée. HAProxy va plus loin sur la répartition de charge et le suivi de santé des backends, Caddy échange une part de contrôle contre une gestion automatique des certificats, et l'usage en conteneur impose ses propres contraintes de résolution DNS.

Le générateur Mozilla est le raccourci le plus fiable pour une section TLS à jour : il produit les directives ssl_protocols et ssl_ciphers correspondant au niveau de compatibilité navigateur retenu. Attention en revanche à docs.nginx.com, qui documente la version commerciale NGINX Plus et décrit des directives absentes de la version open source.

Ces réponses courtes reprennent l'installation par distribution, la structure d'un server block, la mise en place du reverse proxy et du load balancing, l'activation de Let's Encrypt, ainsi que le diagnostic des erreurs 502 et 403.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn