Changer le thème

Guide complet du reverse proxy Nginx : upstream, mise en tampon et timeouts

Easton editorial illustration: one large reverse-proxy gate distributing traffic to three upstream servers

Le téléphone vibre sans arrêt — alerte production.

Les logs sont pleins de 502 Bad Gateway. Le backend tourne, mais les timeouts Nginx sont trop courts : au pic de trafic, les requêtes sont coupées avant la fin du traitement. Ce proxy_read_timeout 60s avait été posé à la va-vite.

Après cet incident, une semaine à creuser les trois modules clés du reverse proxy Nginx : équilibrage upstream, mise en tampon proxy buffer et configuration des timeouts. Bien réglés, le reverse proxy encaisse dix fois plus de trafic ; mal réglés, c’est l’alerte de 3 h du matin.

Cet article rassemble les pièges, le debug et la logique derrière chaque paramètre. Backend, ops ou simple curiosité sur Nginx — vous devriez gagner du temps.


Upstream et équilibrage de charge : bien plus que « distribuer des requêtes »

Syntaxe de base

Le bloc upstream est le cœur de l’équilibrage Nginx. La forme minimale :

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

Simple en surface — un groupe de backends, un proxy_pass. En production, il faut aussi : que faire si un serveur tombe ? Donner plus de trafic aux machines plus puissantes ? Maintenir les longues connexions ?

Quatre algorithmes, quatre cas d’usage

Par défaut, Nginx utilise le round-robin : distribution séquentielle. Équitable, pas intelligent.

Pour les longues connexions — WebSocket, pools de connexions DB — le round-robin peut faire exploser les connexions sur une machine. least_conn convient mieux :

upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Nginx suit les connexions actives par serveur et envoie la nouvelle requête au moins chargé. Sur un projet WebSocket temps réel, le round-robin avait fait monter la RAM d’un nœud — least_conn a uniformisé la charge.

Autre cas : après login, les requêtes doivent retourner sur le même serveur (session locale). ip_hash sert à ça :

upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Même IP client → même backend. Limite : si ce serveur tombe, la session disparaît. Mieux vaut Redis pour les sessions ; ip_hash reste une solution temporaire.

Quatrième option : hash cohérent, fréquent pour le cache distribué :

upstream backend {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Nginx crée 160 nœuds virtuels par unité de poids et route selon le hash de l’URI. Même URI → même machine, meilleur taux de hit cache.

Pondération : machines hétérogènes

Configurations inégales : 32 Go / 8 cœurs d’un côté, 16 Go / 4 cœurs de l’autre. Le round-robin équitable gaspille la puissance.

upstream backend {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;
}

weight=3 reçoit trois fois plus de requêtes. Les machines capables travaillent plus — logique.

Paramètre backup pour le serveur de secours :

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080 backup;
}

Le backup n’entre en jeu que si les autres sont down — le remplaçant sur le banc.

Pool keepalive : le secret du doublement de perf

Souvent négligé. Par défaut, Nginx ouvre une TCP vers le backend à chaque requête et la ferme après la réponse.

Coût élevé : handshake à l’ouverture, fermeture en quatre temps. En forte concurrence, le pool keepalive réutilise les connexions.

Exemple :

upstream backend {
    server 192.168.1.10:8080;
    keepalive 32;  # 32 connexions idle max par worker
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Deux points :

  1. keepalive 32 : connexions idle max par worker
  2. Obligatoire : proxy_http_version 1.1 et Connection "" — HTTP/1.0 ne gère pas la persistance

Test API : sans keepalive ~2000 QPS, avec keepalive 4000+. Le double n’est pas une exagération.

Gain de QPS
Source: Données mesurées : après activation du pool keepalive

Attention : ne montez pas keepalive trop haut. En test, 100 connexions sur un seul conteneur ECS ont saturé le backend. Formule prod :

keepalive ≈ QPS total ÷ durée moyenne par requête ÷ nombre de workers

Ex. QPS 10000, latence 50 ms, 4 workers :

10000 × 0.05 ÷ 4 = 125

~125 est raisonnable.

Contrôle de santé : retrait automatique des backends down

La version open source ne propose que le contrôle passif — marquage après échec de requête :

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
    }
}

proxy_next_upstream : retry sur erreur connexion, timeout ou 502/503/504. proxy_next_upstream_tries 3 : max 3 backends testés.

Limite : détection tardive — il faut un échec réel. Pour une haute dispo, le contrôle actif NGINX Plus est préférable :

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        health_check interval=5s fails=3 passes=2;
    }
}

Sonde toutes les 5 s ; 3 échecs → unhealthy ; 2 succès → retour healthy.


Proxy Buffer : allié ou obstacle ?

À quoi sert le buffering

Nginx ne renvoie pas la réponse backend directement au client : elle passe d’abord par un tampon.

Pourquoi ? La vitesse client est imprévisible. Le backend peut envoyer vite ; si le client est lent, Nginx attend. Avec un buffer, Nginx stocke la réponse et la sert progressivement — le backend peut traiter la requête suivante plus tôt.

Contrepartie : consommation mémoire, surtout gros corps de réponse et forte concurrence.

Trois paramètres clés

proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;

Au début, les noms se ressemblent. En résumé :

  • proxy_buffer_size : tampon des en-têtes de réponse, un par requête
  • proxy_buffers : tableau pour le corps ; format nombre taille_unitaire
  • proxy_busy_buffers_size : portion en cours d’envoi client ; max la moitié du total buffers

Ex. proxy_buffers 8 32k → 256k total. proxy_busy_buffers_size 64k = un quart, conforme.

Quand ajuster ?

En-têtes énormes (beaucoup de cookies) → erreur « upstream sent too big header ». Augmentez proxy_buffer_size :

proxy_buffer_size 16k;

Gros JSON en réponse → agrandissez les buffers :

proxy_buffers 16 64k;

Cas où il faut couper le buffering

Server-Sent Events (SSE) : flux d’événements continu ; le buffering retarde le client :

location /events {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
}

proxy_read_timeout 86400s (24 h) : connexion longue, pas de coupure par timeout.

WebSocket : communication bidirectionnelle temps réel :

location /ws {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400s;
}

Upload volumineux : 1 Go côté client ; tout bufferiser avant forward → explosion RAM. Désactivez le buffering requête :

location /upload {
    proxy_pass http://backend;
    proxy_request_buffering off;
    client_max_body_size 1G;
}

proxy_request_buffering off : transfert en flux, réception et envoi simultanés.


Timeouts : la logique derrière les paramètres

Trois timeouts, trois rôles

proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;

Noms proches, fonctions distinctes :

  • proxy_connect_timeout : délai pour établir la TCP vers le backend (réseau lent, firewall…)
  • proxy_read_timeout : après connexion, délai entre deux lectures de données backend
  • proxy_send_timeout : limite pour envoyer le corps de requête au backend

Piège courant : proxy_read_timeout n’est pas un timeout total, mais l’intervalle entre deux lectures. Traitement 5 min avec heartbeats réguliers → 60s peut suffire. Silence total 5 min → proxy_read_timeout 300s.

Lien avec 502 et 504

Leçon de l’alerte de 3 h :

  • 502 Bad Gateway : Nginx n’atteint pas le backend — service down, port fermé, firewall
  • 504 Gateway Timeout : connecté, mais pas de données assez vite

Ex. proxy_connect_timeout 10s : backend met 15 s à accepter → 502. Connexion rapide, traitement 2 min, proxy_read_timeout 60s → 504.

Stratégies par scénario

API : 30–60 s en général. Timeout court pour repérer les lenteurs :

proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;

Traitement fichiers : export, PDF — plusieurs minutes possibles :

proxy_connect_timeout 10s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;

Flux longs : live, WebSocket, SSE — 24 h est normal :

proxy_read_timeout 86400s;

Dépannage 502/504 en pratique

Causes fréquentes

Cas rencontrés :

  1. Backend réellement down : crash, port occupé, OOM
  2. Pool de connexions saturé : backend plein, Nginx ne se connecte plus
  3. Timeouts trop courts : comme l’alerte de 3 h — 60s alors que le backend met 2 min
  4. Réseau / firewall : règles sécurité, iptables

Diagnostic via les logs

Première étape : error_log :

error_log /var/log/nginx/error.log warn;

Messages typiques :

upstream timed out (110: Connection timed out) while reading response header from upstream

504 — timeout de lecture.

connect() failed (111: Connection refused) while connecting to upstream

502 — connexion refusée, rien n’écoute.

Format de log personnalisé pour l’état upstream :

log_format upstream_status '$status $upstream_status $upstream_response_time';

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

Sortie du type 200 200, 200, 502 0.5, 1.2, 3.0 — statut et latence par backend.

Solutions types

Scénario 1 : backend lent, 504 fréquents

Augmentez proxy_read_timeout et vérifiez que le backend termine vraiment. Ajustez aussi les timeouts côté application, pas seulement Nginx.

Scénario 2 : connexion refusée, 502

Vérifiez processus, port en écoute, règles firewall :

netstat -tlnp | grep 8080
ps aux | grep your_app

Scénario 3 : saturation connexions en pic

Augmentez les limites backend ou activez keepalive upstream pour réduire la création de connexions.


Bonnes pratiques de performance

Configuration worker

Nginx est multi-processus. worker_processes = nombre de processus, souvent égal aux cœurs CPU :

worker_processes auto;

auto détecte les cœurs. Machine 8 cœurs → 8 workers.

worker_connections : connexions max par worker :

events {
    worker_connections 4096;
}

Concurrence théorique = worker_processes × worker_connections. 8 × 4096 = 32768. Limité aussi par les descripteurs de fichiers système.

Trois réglages TCP

sendfile on;
tcp_nopush on;
tcp_nodelay on;

Combinaison performante :

  • sendfile on : transfert fichier via le noyau, sans copie user-space
  • tcp_nopush on : avec sendfile, envoi par lots plutôt que paquet par paquet
  • tcp_nodelay on : petits paquets envoyés tout de suite

Fichiers statiques : débit +30 % mesuré avec ces trois options.

30 %+
Gain de débit
Source: Données mesurées : sendfile + tcp_nopush + tcp_nodelay activés

Autres optimisations

Compression gzip : texte compressé, bande passante économisée :

gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1024;

Limites descripteurs : en forte concurrence, ulimit peut bloquer :

ulimit -n

Si 1024 seulement, augmentez dans /etc/security/limits.conf :

* soft nofile 65535
* hard nofile 65535

Exemple de configuration complète

Modèle recommandé pour la production :

# Configuration de base
worker_processes auto;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    # Optimisation TCP
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keepalive
    keepalive_timeout 30;
    keepalive_requests 100;

    # Buffers
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;

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

    # gzip
    gzip on;
    gzip_types text/plain text/css application/json;

    upstream backend {
        least_conn;
        server 192.168.1.10:8080 weight=3;
        server 192.168.1.11:8080 weight=2;
        server 192.168.1.12:8080 backup;
        keepalive 32;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;

            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
        }

        # SSE
        location /events {
            proxy_pass http://backend;
            proxy_buffering off;
            proxy_read_timeout 86400s;
        }
    }
}

Conclusion

Trois piliers :

  1. Upstream : bon algorithme, pool keepalive, contrôle de santé
  2. Buffers : comprendre les trois paramètres ; couper le buffering si besoin
  3. Timeouts : rôle de chaque directive ; stratégie selon le scénario

L’alerte de 3 h m’a appris : la config Nginx n’est pas du remplissage de paramètres. Chaque valeur a une logique — la comprendre évite les pièges.

Débutant ? Partez des défauts, ajustez au problème — pas un proxy_read_timeout 60s posé au hasard en prod. Déjà passé par là ? Ce guide devrait relier vos bouts d’expérience.

En relisant ma prod actuelle : keepalive 32, proxy_read_timeout 120s, least_conn. Plus d’alerte à 3 h du matin.


FAQ

proxy_read_timeout est-il un timeout total ou l'intervalle entre deux lectures ?
L'intervalle entre deux opérations de lecture. Si le backend envoie des données en continu pendant le traitement (heartbeat par ex.), proxy_read_timeout 60s suffit même pour 5 minutes au total. Si le backend reste silencieux 5 minutes, il faut 300s.
Quand faut-il désactiver proxy_buffering ?
Trois cas obligatoires :

• Server-Sent Events (SSE) : push temps réel, le buffering retarde les messages
• WebSocket : communication bidirectionnelle temps réel, flux continu requis
• Upload de gros fichiers : éviter l'explosion mémoire, transférer au fil de l'eau
Quelle valeur pour keepalive ?
Formule : keepalive ≈ QPS total × durée moyenne par requête ÷ nombre de workers. Ex. QPS 10000, latence 50 ms, 4 workers → environ 125. Ne montez pas trop — j'ai déjà mis 100 et saturé le backend.
Quelle différence entre 502 et 504 ?
502 Bad Gateway : Nginx n'atteint pas le backend (service down, port fermé, firewall). 504 Gateway Timeout : connexion établie mais réponse trop lente. Diagnostic : 502 → processus et ports ; 504 → timeouts et temps de traitement backend.
Quel algorithme d'équilibrage choisir ?
Selon le cas :

• Round-robin (défaut) : services sans état, répartition équitable
• least_conn : longues connexions (WebSocket, pools DB)
• ip_hash : affinité de session (solution temporaire, Redis préférable)
• hash : cache distribué, meilleur taux de hit
Comment résoudre upstream sent too big header ?
Augmentez proxy_buffer_size. Des en-têtes trop volumineux (nombreux cookies par ex.) dépassent le tampon par défaut de 4k. proxy_buffer_size 16k règle souvent le problème.
Pourquoi sendfile + tcp_nopush + tcp_nodelay améliorent les performances ?
sendfile contourne l'espace utilisateur pour un transfert noyau ; tcp_nopush regroupe les envois ; tcp_nodelay envoie les petits paquets sans attendre. Combinés, débit fichiers statiques +30 % mesuré.

8 min de lecture · Publié le: 30 mars 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog