Aller au contenu
medium

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

28 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

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

Imaginez Nginx comme un switch réseau intelligent (L7) :

Concept NginxAnalogieExemple concret
server {}Un port d’écoute du switchserver { listen 80; server_name api.example.com; }
location {}Les règles de routage sur ce portlocation /api { } route vers le backend API
proxy_passLa règle de forwardingLa requête est forwardée vers votre app Node.js
rootServir depuis le cache localSert directement les fichiers HTML/CSS

Nginx reçoit les paquets (requêtes), les route selon les règles (routing), et les forward vers le bon backend (upstream ou fichiers).

📜 Pour la culture : historique de Nginx

Nginx a été créé par Igor Sysoev en 2004 pour résoudre le problème des 10 000 connexions simultanées (C10K) que les serveurs de l’époque (Apache) géraient mal. Son architecture asynchrone et événementielle lui permet de gérer des milliers de connexions avec peu de ressources.

En 2019, F5 Networks a acquis Nginx Inc. pour 670 millions de dollars. Aujourd’hui, Nginx est l’un des serveurs web les plus utilisés au monde, propulsant des sites comme Netflix, Dropbox et WordPress.com.

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

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)
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;
}

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)
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.

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.

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
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 pasVérifier que l’app backend tourne
504 Gateway TimeoutBackend trop lentAugmenter proxy_read_timeout
”Address already in use”Port déjà utiliséss -tlnp | grep :80 pour identifier
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)"

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;
}
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;
}
}
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;
}
location / {
# Autoriser seulement GET, POST, HEAD
limit_except GET POST HEAD {
deny all;
}
root /var/www/html;
}
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.

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.

server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
location / {
root /var/www/app;
try_files $uri $uri/ /index.html;
}
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;
}
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 /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;
}
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;
}
location /admin/ {
auth_basic "Zone Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
location /admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://backend;
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
location / {
if (-f /var/www/maintenance.html) {
return 503;
}
proxy_pass http://backend;
}
error_page 503 /maintenance.html;
location = /maintenance.html {
root /var/www;
}
location /upload/ {
client_max_body_size 100M;
proxy_pass http://backend;
proxy_read_timeout 300s;
}
upstream backend_pool {
least_conn;
server backend1:3000;
server backend2:3000;
server backend3:3000 backup;
}
location / {
proxy_pass http://backend_pool;
}
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;
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
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)
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)
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
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
CommandeDescription
nginx -tTester la configuration
nginx -TDump config complète (includes résolus)
nginx -s reloadRecharger sans interruption
nginx -s stopArrêter immédiatement
  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

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.