Aller au contenu principal

Nginx

· 6 minutes de lecture

Nginx est un serveur web événementiel conçu pour gérer un grand nombre de connexions simultanées avec une faible empreinte mémoire. Contrairement aux serveurs multi-thread (Apache prefork), il utilise un modèle non-bloquant à base de boucle d'événements — chaque worker process gère des milliers de connexions sans créer un thread par connexion. Cette architecture le rend particulièrement adapté comme reverse proxy en frontal d'une infrastructure.

Modèle de configuration

La configuration Nginx s'organise hiérarchiquement : contexte http → blocs server → blocs location. Chaque server écoute sur un port et un nom de domaine. Les location définissent comment traiter les requêtes selon leur URI.

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

location / {
root /var/www/html;
index index.html;
}
}
}

Les directives définies dans un contexte parent sont héritées par les contextes enfants, sauf si elles sont redéfinies localement.

Matching des blocs location

Nginx sélectionne le bloc location le plus spécifique selon des règles de priorité précises :

location = /exact {        # 1. correspondance exacte (priorité maximale)
...
}

location ^~ /static/ { # 2. préfixe prioritaire (interrompt la recherche regex)
...
}

location ~* \.(jpg|png)$ { # 3. regex insensible à la casse
...
}

location /api/ { # 4. préfixe standard (priorité la plus longue gagne)
...
}

location / { # 5. fallback (correspond à tout)
...
}

L'ordre d'évaluation : exact (=) → préfixe prioritaire (^~) → regex (~ ou ~*) → préfixe standard (le plus long). try_files $uri $uri/ =404 est le pattern standard pour les SPA — tenter de servir le fichier exact, le répertoire, ou retourner 404.

Reverse proxy

En tant que reverse proxy, Nginx reçoit les requêtes clients et les transmet à un backend applicatif. Le backend ne voit que l'IP de Nginx — les en-têtes X-Forwarded-* propagent le contexte de la requête originale.

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

location / {
proxy_pass http://127.0.0.1:8000;

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;

# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

# Buffers (évite d'ouvrir la connexion upstream trop longtemps)
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}

proxy_buffering permet à Nginx de lire la réponse complète du backend en mémoire avant de l'envoyer au client — utile quand les clients sont lents (mobile, connexion faible) pour libérer rapidement le worker backend. Le désactiver pour les streams ou les Server-Sent Events.

Load balancing

Nginx distribue le trafic entre plusieurs instances d'un backend via un bloc upstream.

upstream api_backend {
# Algorithmes : round-robin (défaut), least_conn, ip_hash, hash
least_conn;

server 10.0.0.1:8000 weight=3; # 3x plus de trafic
server 10.0.0.2:8000;
server 10.0.0.3:8000 backup; # utilisé si les autres tombent
server 10.0.0.4:8000 max_fails=3 fail_timeout=30s; # health check passif
}

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

location / {
proxy_pass http://api_backend;
}
}
AlgorithmeComportementUsage
round-robintour à tourcas général
least_conninstance avec le moins de connexions activesrequêtes longues (WebSocket, upload)
ip_hashmême backend pour un IP donnésessions applicatives sans sticky session
hash $request_urimême backend pour une URI donnéecache cohérent

max_fails et fail_timeout activent le health check passif : après max_fails erreurs en fail_timeout secondes, le backend est marqué indisponible pendant fail_timeout secondes. Le health check actif (sondage régulier) est disponible uniquement dans Nginx Plus (version commerciale).

Terminaison SSL

Nginx centralise la terminaison TLS : les connexions clients sont chiffrées jusqu'à Nginx, qui communique ensuite en HTTP clair avec le backend sur le réseau interne.

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

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

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

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:!aNULL:!MD5';

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # TICKET TICKETS : risque de forward secrecy si la clé de ticket fuite

ssl_stapling on;
ssl_stapling_verify on;

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;

location / {
proxy_pass http://127.0.0.1:8000;
}
}

ssl_session_cache shared:SSL:10m partage le cache de sessions TLS entre tous les workers — évite la répétition du handshake complet pour chaque requête. 10m supporte environ 40 000 sessions simultanées.

Compression gzip

http {
gzip on;
gzip_comp_level 5; # 1 (faible) à 9 (fort), 5 est un bon compromis
gzip_min_length 256; # ne pas compresser les très petits fichiers
gzip_proxied any; # compresser même pour les requêtes via proxy
gzip_vary on; # ajouter "Vary: Accept-Encoding" au header

gzip_types
application/javascript
application/json
application/xml
text/css
text/html
text/plain
image/svg+xml;
}

gzip_vary on est important pour les caches intermédiaires (CDN, Nginx lui-même) : ils savent qu'ils doivent stocker deux versions de la ressource (compressée et non compressée) selon le client.

Cache des réponses proxy

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

server {
location /api/ {
proxy_pass http://api_backend;
proxy_cache api_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key $scheme$host$request_uri;
proxy_cache_use_stale error timeout updating;

add_header X-Cache-Status $upstream_cache_status; # HIT / MISS / BYPASS
}
}
}

proxy_cache_use_stale permet de servir une réponse expirée si le backend est indisponible ou lent — comportement de "circuit breaker" basique qui améliore la résilience.

Rate limiting

http {
# Définir une zone de limitation : 10 Mo de mémoire, 10 req/s par IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
# burst=20 : file d'attente de 20 requêtes
# nodelay : traiter les requêtes en burst immédiatement (sans délai artificiel)
proxy_pass http://api_backend;
}
}
}

Sans nodelay, les requêtes en burst sont traitées au rythme du rate (une toutes les 100ms pour 10r/s). Avec nodelay, les burst requêtes sont traitées immédiatement — mais les suivantes retournent 503 tant que la file est pleine.

Logs et format personnalisé

http {
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';

access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
}

$request_time mesure le temps total de traitement de la requête côté Nginx. $upstream_response_time mesure le temps passé à attendre la réponse du backend — utile pour distinguer les lenteurs réseau des lenteurs applicatives.

Rechargement de la configuration

# Vérifier la syntaxe avant de recharger
nginx -t

# Recharger sans interrompre les connexions en cours
sudo systemctl reload nginx

# Ou directement via le signal
sudo nginx -s reload

nginx -t teste la syntaxe et charge les fichiers includes sans appliquer les changements. À utiliser systématiquement avant un reload en production — une erreur de syntaxe avec reload n'interrompt pas le process en cours, mais un restart sur une config invalide stoppe le service.