Équilibrage de charge Nginx en pratique : upstream et contrôles de santé

Le téléphone vibre sans arrêt. Sur le tableau de bord, la ligne de backend1 est toute rouge — un seul serveur applicatif est tombé.
Lors du Double 11 de cette année-là, nous n’avions que deux backends. La config Nginx disait upstream backend { server backend1; server backend2; } — symétrique en apparence. Mais backend1 prenait ~70 % du trafic : premier dans le fichier, round-robin par défaut, sans poids ni contrôle de santé.
Quand backend1 est tombé, les commandes continuaient d’y être envoyées. Nginx ne savait pas qu’il était mort. Résultat côté utilisateur : pages 500. Il a fallu 15 minutes pour que l’ops le retire manuellement de l’upstream.
Depuis, j’ai creusé le sujet : l’upstream Nginx ne se résume pas à une liste d’adresses. Pondération, santé, bascule — c’est ce dont la prod a besoin. Cet article rassemble les pièges vécus et les réglages retenus, y compris le contrôle actif avec Nginx open source.
1. Configuration upstream de base : du mono-serveur au cluster
Le bloc upstream regroupe plusieurs machines en un groupe logique. Les paramètres sont plus riches qu’on ne le croit souvent.
upstream backend {
zone backend 64k;
server backend1.example.com weight=3 max_fails=2 fail_timeout=30s;
server backend2.example.com;
server backup1.example.com backup;
}
Ligne par ligne :
zone backend 64k : mémoire partagée entre workers pour l’état des backends (qui est up, qui est down). 64k pour démarrer ; augmentez si vous avez beaucoup de serveurs. Sans zone, chaque worker vit sa vie — source classique d’incohérence.
weight=3 : backend1 reçoit trois parts sur quatre, backend2 une (défaut 1). Utile si les machines ne sont pas homogènes — ex. 8 cœurs / 16 Go vs 4 cœurs / 8 Go.
max_fails=2 : dans la fenêtre fail_timeout, deux échecs marquent le serveur indisponible. La valeur par défaut 1 est trop nerveuse (un micro-blocage réseau suffit). En prod : 2 ou 3.
fail_timeout=30s : double rôle — fenêtre de comptage des échecs (30 s) et délai avant nouvel essai après marquage down. 10 s par défaut peut être court pour des services à démarrage lent.
backup : ne reçoit du trafic que si tous les principaux sont down. Idéal pour une machine plus légère en secours.
down marque un serveur hors ligne pour la maintenance :
server backend3.example.com down; # maintenance temporaire
Beaucoup oublient zone : un worker voit la panne, les autres non. Avec zone, l’état est aligné.
2. Cinq stratégies d’équilibrage : laquelle choisir ?
Le round-robin par défaut suffit-il ? Parfois oui.
J’ai vu des projets tourner des années en round-robin sans souci. WebSocket, panier e-commerce, cache : là, le défaut coince. Tableau de choix :
| Scénario | Stratégie | Raison |
|---|---|---|
| API sans état | round-robin | Répartition simple |
| WebSocket | least_conn | Évite la surcharge sur une machine |
| Panier e-commerce | ip_hash | Même client → même serveur |
| Proxy cache | hash key=$uri | Limite invalidation et trous de cache |
| Environnement de test | random | Rapide à configurer |
round-robin (par défaut)
Sans directive, alternance séquentielle :
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
Adapté aux services sans état — la plupart des API REST.
least_conn (moins de connexions)
Envoie vers le serveur avec le moins de connexions actives :
upstream websocket_app {
least_conn;
server ws1.example.com:8080;
server ws2.example.com:8080;
}
Typique WebSocket : une connexion longue par utilisateur. Le round-robin peut empiler les longues connexions sur une machine ; least_conn rééquilibre en temps réel.
ip_hash
Hash de l’IP client : même IP → même serveur :
upstream shopping_cart {
ip_hash;
server cart1.example.com;
server cart2.example.com;
}
Session locale : articles ajoutés sur cart1, requête suivante sur cart2 sans session distribuée = panier vide. ip_hash corrige cela.
Limite : si un serveur tombe, les clients hashés dessus sont réassignés — perte de session locale. À coupler avec stockage session partagé, ou pour données peu critiques.
hash (hachage cohérent)
Clé personnalisée, algorithme cohérent :
upstream cache_proxy {
hash $uri consistent;
server cache1.example.com;
server cache2.example.com;
}
Proxy cache : $uri comme clé ; consistent limite le remapping quand on ajoute/retire un nœud — meilleur taux de hit.
random
Répartition aléatoire :
upstream test_backend {
random;
server test1.example.com;
server test2.example.com;
}
OK en test. Peu de contrôle en production.
En pratique : round-robin ou least_conn couvrent la majorité des apps web. ip_hash et hash répondent à des cas précis — pas pour « faire joli ».
3. Contrôle passif : max_fails et fail_timeout
« Passif » : pas de sonde dédiée ; Nginx déduit la santé des requêtes réelles — comme juger un voisin à ses allers-retours, pas en frappant à sa porte.
upstream backend {
server backend1.example.com max_fails=3 fail_timeout=30s;
server backend2.example.com max_fails=3 fail_timeout=30s;
}
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}
proxy_next_upstream définit l’échec : erreur de connexion, timeout, 5xx. Nginx réessaie sur le suivant et incrémente le compteur d’échecs.
Trois échecs en 30 s → serveur marqué down, plus de trafic pendant 30 s, puis un essai de reprise.
Limite : lenteur. Il faut des échecs utilisateurs réels — trois clients mal servis avant retrait.
Pire au redémarrage : max_fails=1 peut exclure un serveur encore en init.
Recommandations :
- max_fails à 2 ou 3
- fail_timeout ≥ 30 s
- proxy_next_upstream avec la liste complète des codes
Avantage : natif, simple. Inconvénient : l’utilisateur paie le premier échec. Pour anticiper, passez à l’actif.
4. Contrôle actif : NGINX Plus et alternative open source
Logique : sondes périodiques (ex. GET /health), retrait avant que le trafic client échoue.
Officiel : NGINX Plus, ~3 675 $/instance/an — dix instances ≈ 36 750 $/an, cher pour beaucoup d’équipes.
Open source : nginx_upstream_check_module (équipe Taobao). Recompilation Nginx requise, fonctionnalités solides :
| Fonctionnalité | NGINX Plus | nginx_upstream_check_module |
|---|---|---|
| Prix | 3 675 $/an | Gratuit (open source) |
| Sonde HTTP | Oui | Oui |
| Sonde TCP | Oui | Oui |
| Sonde MySQL | Non | Oui |
| Sonde FastCGI | Non | Oui |
| Page d’état | Oui | Oui (check_status) |
MySQL et FastCGI manquent chez Plus — utile pour PHP-FPM ou MySQL.
Exemple de configuration
upstream backend {
server backend1.example.com:8080;
server backend2.example.com:8080;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
location / {
proxy_pass http://backend;
}
location /upstream_status {
check_status json;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
Paramètres :
- interval=3000 : sonde toutes les 3 s
- rise=2 : deux succès consécutifs → healthy
- fall=5 : cinq échecs → down
- timeout=1000 : timeout sonde 1 s
- type=http : aussi tcp, ssl_hello, mysql, ajp, fastcgi
check_http_send : contenu de la sonde (GET /health). Le backend doit répondre 2xx/3xx.
check_http_expect_alive : codes considérés sains.
check_status : JSON pour Prometheus/Zabbix — protégez l’URL (allow/deny).
Installation du module
# Télécharger le module
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
# Télécharger les sources Nginx
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
# Patch (selon version)
cd nginx-1.24.0
patch -p1 < ../nginx_upstream_check_module/check_1.20.1+.patch
# Compilation
./configure --add-module=../nginx_upstream_check_module
make && make install
Sous Docker : image maison ou image communautaire avec le module.
5. Production : sécurité et supervision
Trois principes qui évitent les erreurs courantes :
Sécurité : trois points
1. Restreindre check_status
La page expose la liste des backends et leur état — topologie interne et cibles affaiblies visibles de l’extérieur.
location /upstream_status {
check_status json;
allow 127.0.0.1; # local
allow 10.0.0.0/8; # intranet
deny all;
}
Ou uniquement les IP de votre stack de monitoring.
2. Port dédié aux sondes
Sondes toutes les 3-5 s sur le port métier → logs /health saturés.
Deux ports : métier (8080) et santé (8888) — réponse minimale, sans log métier.
check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;
3. /health sans données sensibles
Statut HTTP suffit — pas de version, config ou mémoire.
# Exemple backend
@app.route('/health')
def health():
return '', 200 # code seulement
Intégration monitoring : sortie JSON
curl http://127.0.0.1/upstream_status
Exemple :
{
"servers": {
"total": 3,
"generation": 12,
"server": [
{"index": 0, "name": "10.0.0.1:8080", "status": "up", "rise": 5, "fall": 0, "type": "http"},
{"index": 1, "name": "10.0.0.2:8080", "status": "up", "rise": 3, "fall": 0, "type": "http"},
{"index": 2, "name": "10.0.0.3:8080", "status": "down", "rise": 0, "fall": 5, "type": "http"}
]
}
}
generation compte les reload de config upstream — utile pour vérifier qu’un déploiement a bien été pris en compte.
Réglage des seuils
interval ≥ 3000 ms
3-5 s suffisent ; détection en secondes, charge backend raisonnable.
rise / fall
- rise trop bas (1) → oscillations au boot
- fall trop bas (1) → retrait sur un pic réseau
Valeurs éprouvées : rise=2, fall=3 ou 5.
Configuration production complète
upstream web_app {
zone web_app 64k;
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080;
server 10.0.0.3:8080 backup;
check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;
check_http_send "GET /health HTTP/1.1\r\nHost: app.example.com\r\n\r\n";
check_http_expect_alive http_2xx;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://web_app;
proxy_set_header Host $host;
proxy_next_upstream error timeout http_502 http_503 http_504;
}
location /upstream_status {
check_status json;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
Récap : zone pour les workers ; sonde active toutes les 5 s sur port dédié ; check_status intranet only ; proxy_next_upstream pour basculer hors serveur down.
Conclusion
Après l’incident Double 11 : zone, max_fails/fail_timeout, puis nginx_upstream_check_module. Panne détectée et retirée en ~5 s — peu d’erreurs côté client.
Stratégies : sans état → round-robin ou least_conn ; avec état → ip_hash ou hash. Santé : obligatoire en prod ; module open source + accès verrouillé sur check_status.
Encore en round-robin pur sans santé ? Commencez par le passif (max_fails + fail_timeout) — changement minimal, effet rapide. Puis activez l’actif après validation en test.
FAQ
À quoi sert la directive zone dans un upstream Nginx ?
Quelle différence entre contrôle de santé passif et actif ?
nginx_upstream_check_module ou NGINX Plus ?
round-robin ou least_conn ?
Comment régler rise et fall ?
Pourquoi restreindre l'accès à check_status ?
7 min de lecture · Publié le: 27 avr. 2026 · Mis à jour le: 27 juil. 2026
Guide pratique Nginx
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Configuration Nginx SSL/TLS en pratique : du certificat HTTPS au durcissement A+
Configurer Nginx HTTPS de zéro : certificat Let's Encrypt, durcissement TLS 1.3, modèle A+ SSL Labs, optimisation OCSP Stapling — guide complet à jour pour 2026.
Partie 3 sur 6
Suivant
Upstream dynamique Nginx : découverte de services en temps réel avec Lua
Architecture en trois couches OpenResty pour upstream dynamique, comparaison des solutions de health check, code d'intégration Consul/Nacos/etcd pour la découverte de services en environnement conteneurisé.
Partie 5 sur 6



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire