Nginx Lastverteilung in der Praxis: upstream-Konfiguration und Health Checks

Das Handy vibriert ununterbrochen. Im Monitoring leuchtet backend1 rot – ein App-Server ist ausgefallen.
Beim großen November-Sale hatten wir nur zwei Backend-Server. In der Nginx-Konfiguration stand upstream backend { server backend1; server backend2; } – symmetrisch, aber irreführend. backend1 trug etwa 70 % des Traffics, weil es in der Datei an erster Stelle stand und der Default round-robin ohne Gewichtung und ohne Health Check lief.
Als backend1 ausfiel, gingen Bestellanfragen weiter an den toten Server. Nginx wusste nichts vom Ausfall und leitete weiter. Nutzer sahen 500-Fehler. Erst nach 15 Minuten manuellem Entfernen aus dem upstream war Ruhe.
Danach haben wir nachgerüstet: Nginx upstream ist mehr als eine Serverliste. Gewichtung, Health Checks und Failover gehören in Produktion dazu. Dieser Artikel fasst unsere Fehler und die spätere Konfiguration zusammen – inklusive aktiver Health Checks mit Open-Source-Nginx.
1. upstream-Grundlagen: vom Einzelserver zum Cluster
Der upstream-Block bündelt mehrere Server zu einer logischen Gruppe. Die verfügbaren Parameter sind reichhaltiger, als viele annehmen.
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;
}
Zeile für Zeile:
zone backend 64k: Shared-Memory-Bereich. Worker-Prozesse teilen Backend-Status (wer erreichbar ist, wer ausgefallen). 64k ist ein Startwert; bei vielen Servern vergrößern. Ohne zone verwaltet jeder Worker seinen eigenen Status – ein häufiger Fehler.
weight=3: Gewichtung. backend1 hat 3, backend2 standardmäßig 1. Von vier Anfragen gehen drei an backend1, eine an backend2. Sinnvoll bei unterschiedlicher Hardware – z. B. backend1 mit 8 Kernen/16 GB, backend2 mit 4 Kernen/8 GB.
max_fails=2: Schwellwert für Fehlversuche. Innerhalb des fail_timeout-Fensters markiert Nginx den Server nach zwei Fehlschlägen als ungesund. Default 1 ist zu empfindlich – ein Netzwerk-Hiccup reicht. In Produktion 2 oder 3 setzen.
fail_timeout=30s: Doppelte Bedeutung. Erstens das Zeitfenster für Fehlzählung; zweitens die Wartezeit, bis Nginx erneut verbindet, nachdem der Server als down markiert wurde. Default 10s reicht für langsam startende Dienste oft nicht.
backup: Reserve-Server. Er wird erst genutzt, wenn alle primären Server ausfallen – z. B. schwächerer Server als Fallback.
Mit down markieren Sie einen Server manuell offline, typisch für Wartung:
server backend3.example.com down; # vorübergehend offline
In der Praxis wird zone oft vergessen. Dann sieht ein Worker einen Ausfall, andere senden weiter – mit zone ist der Status synchron.
2. Fünf Lastverteilungsstrategien: wann welche?
Reicht round-robin immer? Kommt darauf an.
Viele Projekte laufen jahrelang mit Default-Round-Robin ohne Probleme. Bei WebSocket-Long-Living-Connections, Warenkorb-Sessions oder Cache-Penetration stößt die Default-Strategie an Grenzen. Orientierung:
| Szenario | Empfohlene Strategie | Begründung |
|---|---|---|
| Zustandslose API | round-robin | Gleichmäßige Verteilung, keine Besonderheiten |
| WebSocket-Dienst | least_conn | Verbindungszahl in Echtzeit, keine Überlastung einzelner Knoten |
| E-Commerce-Warenkorb | ip_hash | Gleicher Nutzer immer auf demselben Server |
| Cache-Proxy | hash key=$uri | Weniger Cache-Invalidierung und -Penetration |
| Testumgebung | random | Schnelle Validierung, einfache Konfiguration |
round-robin (Standard)
Ohne Extra-Konfiguration rotiert Nginx sequenziell:
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
Für zustandslose Dienste. Jede Anfrage steht für sich – typisch für REST-APIs.
least_conn (wenigste Verbindungen)
Anfragen gehen an den Server mit den wenigsten aktiven Verbindungen:
upstream websocket_app {
least_conn;
server ws1.example.com:8080;
server ws2.example.com:8080;
}
Klassisch für WebSocket: Eine Long-Living-Connection pro Nutzer, stark schwankende Verbindungszahlen. Round-robin kann einen Knoten mit vielen offenen Verbindungen weiter belasten; least_conn wählt den entlastetsten Server.
ip_hash (IP-Hash)
Hash aus Client-IP – gleiche IP landet immer auf demselben Server:
upstream shopping_cart {
ip_hash;
server cart1.example.com;
server cart2.example.com;
}
Für Session-Konsistenz. Warenkorb auf cart1, nächste Anfrage auf cart2 ohne verteilte Session – Daten weg. ip_hash löst das.
Einschränkung: Fällt ein Server aus, werden betroffene IPs neu gemappt – Sessions gehen verloren. Daher nur bei unkritischen Sessions oder mit Shared-Session-Storage.
hash (Konsistenter Hash)
Eigener Hash-Key, optional konsistenter Hash:
upstream cache_proxy {
hash $uri consistent;
server cache1.example.com;
server cache2.example.com;
}
Ideal für Cache-Proxies. $uri als Key; consistent sorgt dafür, dass beim Hinzufügen/Entfernen von Servern nur ein Teil der Keys umzieht – Cache-Trefferquote bleibt stabil.
random
Zufällige Verteilung:
upstream test_backend {
random;
server test1.example.com;
server test2.example.com;
}
Für Tests ausreichend. In Produktion wegen fehlender Steuerung unüblich.
Praxis: Die meisten Web-Apps kommen mit round-robin oder least_conn aus. ip_hash und hash sind Speziallösungen – nicht der Einsatz um des Einsatzes willen.
3. Passive Health Checks: max_fails und fail_timeout
„Passiv“ heißt: Nginx sendet keine Sondierungen, sondern wertet echte Anfragen aus – wie indirektes Beobachten statt aktiv nachfragen.
max_fails und fail_timeout konfigurieren diesen Mechanismus:
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 definiert Fehler: error (Verbindung), timeout, HTTP 500–504. Nginx leitet weiter und zählt einen Fehler für den aktuellen Server.
Drei Fehler in 30 Sekunden → Server als down. 30 Sekunden lang keine Anfragen dorthin, dann ein erneuter Versuch.
Nachteil: langsame Reaktion. Erst nach drei echten Nutzerfehlern wird abgeschaltet – drei Nutzer sehen bereits Fehler.
Extremfall: Server startet noch; max_fails=1 kann ihn sofort ausschließen und dauerhaft draußen halten.
Empfehlung:
- max_fails auf 2 oder 3 – kurze Netzstörungen tolerieren
- fail_timeout mindestens 30s – Erholung ermöglichen
- proxy_next_upstream vollständig konfigurieren
Vorteil: einfach, in Open-Source-Nginx enthalten. Nachteil: Nutzertraffic als Trigger. Für schnellere Erkennung: aktive Health Checks.
4. Aktive Health Checks: NGINX Plus und Open-Source-Alternativen
Aktive Checks: Nginx sendet regelmäßig Sondierungen (z. B. GET /health) und entfernt fehlerhafte Server, bevor Nutzer betroffen sind.
Offiziell: NGINX Plus – etwa 3.675 USD/Instanz/Jahr. Zehn Instanzen: 36.750 USD/Jahr. Für viele Teams teuer.
Open Source: nginx_upstream_check_module (Taobao-Team). Nginx muss mit Modul kompiliert werden, Funktionsumfang ist stark:
| Merkmal | NGINX Plus | nginx_upstream_check_module |
|---|---|---|
| Preis | 3.675 USD/Jahr | Open Source, kostenlos |
| HTTP-Check | Ja | Ja |
| TCP-Check | Ja | Ja |
| MySQL-Check | Nein | Ja |
| FastCGI-Check | Nein | Ja |
| Statusseite | Ja | Ja (check_status) |
MySQL- und FastCGI-Checks fehlen in NGINX Plus – für PHP-FPM- oder MySQL-Backends oft die bessere Wahl.
Beispiel nginx_upstream_check_module
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;
}
}
Parameter:
- interval=3000: Sondierung alle 3 Sekunden
- rise=2: zwei Erfolge in Folge → gesund (Startphase abfedern)
- fall=5: fünf Fehler in Folge → ungesund (kurze Timeouts tolerieren)
- timeout=1000: Timeout 1 Sekunde
- type=http: HTTP-Sondierung (auch tcp, ssl_hello, mysql, ajp, fastcgi)
check_http_send: Inhalt der Sondierung – hier GET /health. Backend muss /health mit 200 oder 3xx beantworten.
check_http_expect_alive: Gesunde Statuscodes – http_2xx und http_3xx.
check_status: Monitoring-Endpoint; json für Prometheus oder Zabbix. allow/deny ist Pflicht – nicht öffentlich exponieren.
Modul-Installation
Kompilierung nötig:
# Modul-Quellcode
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
# Nginx-Quellcode
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
# Patch (Version beachten)
cd nginx-1.24.0
patch -p1 < ../nginx_upstream_check_module/check_1.20.1+.patch
# Kompilieren
./configure --add-module=../nginx_upstream_check_module
make && make install
Mit Docker: eigenes Image bauen oder Community-Images nutzen.
5. Produktion: Sicherheit und Monitoring
Drei Prinzipien, die oft übersehen werden:
Sicherheit: drei Punkte
1. check_status absichern
Die Seite verrät Backend-Liste und Gesundheitszustand – Topologie und Schwachstellen für Angreifer.
location /upstream_status {
check_status json;
allow 127.0.0.1; # lokal
allow 10.0.0.0/8; # internes Netz
deny all;
}
Strenger: nur definierte Monitoring-IPs.
2. Separater Health-Check-Port
Sondierungen alle 3–5 Sekunden auf dem Business-Port füllen Logs mit /health-Einträgen.
Zwei Ports: Business (z. B. 8080) und Health (z. B. 8888) – letzterer nur Statuscode, kein Business, idealerweise ohne Logging.
check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;
port=8888 wählt den Sondierungsport.
3. Keine sensiblen Daten in /health
Nur Statuscode – keine Version, Konfiguration oder Speichernutzung.
# Backend-Beispiel
@app.route('/health')
def health():
return '', 200 # nur Statuscode
Monitoring: JSON-Ausgabe
check_status unterstützt mehrere Formate; json passt zu Monitoring-Stacks:
curl http://127.0.0.1/upstream_status
Beispielausgabe:
{
"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 zählt Konfigurationsänderungen. Nach upstream-Reload steigt der Wert – Skripte können prüfen, ob die neue Konfiguration aktiv ist.
Parameter-Tuning
interval nicht unter 3000 ms
Zu hohe Frequenz belastet Backends. 3–5 Sekunden reichen; Fehlererkennung bleibt im Sekundenbereich.
rise und fall ausbalancieren
- rise zu klein (z. B. 1): Startphase oszilliert zwischen gesund/ungesund
- fall zu klein (z. B. 1): ein Netz-Hiccup reicht zum Abschalten
Praxiswerte: rise=2, fall=3 oder fall=5.
Vollständige Produktionskonfiguration
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;
}
}
Diese Konfiguration bietet:
- zone für Worker-Synchronisation
- aktive Checks alle 5 Sekunden auf dediziertem Port
- Statusseite nur im internen Netz
- proxy_next_upstream für Failover bei Backend-Fehlern
Fazit
Zurück zum November-Vorfall: Wir ergänzten zone, max_fails/fail_timeout und später nginx_upstream_check_module. Bei Serverausfall erkennt Nginx den Fehler in etwa 5 Sekunden – Nutzer sehen kaum noch Fehlerseiten.
Strategie in einem Satz: zustandslos → round-robin oder least_conn; zustandsbehaftet → ip_hash oder hash. Health Checks in Produktion sind Pflicht; Open Source: nginx_upstream_check_module; check_status unbedingt absichern.
Läuft Ihr Nginx noch mit Default-Round-Robin ohne Checks, starten Sie mit passivem Check (max_fails + fail_timeout) – minimaler Aufwand, schneller Effekt. Danach aktiv upgraden. Erst in der Testumgebung validieren, dann Produktion.
FAQ
Wozu dient die zone-Konfiguration in Nginx upstream?
Was ist der Unterschied zwischen passivem und aktivem Health Check?
nginx_upstream_check_module oder NGINX Plus – was ist besser?
Wann round-robin, wann least_conn?
Wie setzt man rise und fall bei Health Checks?
Warum muss die check_status-Seite geschützt werden?
7 Min. Lesezeit · Veröffentlicht am: 27. Apr. 2026 · Aktualisiert am: 14. Juli 2026
Nginx Praxisleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Nginx SSL/TLS in der Praxis: von HTTPS-Zertifikaten bis A+ Sicherheitshärtung
Nginx HTTPS von Grund auf: Let's Encrypt-Zertifikate, TLS-1.3-Härtung, SSL-Labs-A+-Konfigurationsvorlage, OCSP-Stapling-Performance-Optimierung – vollständiger Leitfaden Stand 2026.
Teil 3 von 6
Nächster
Nginx dynamischer Upstream: Echtzeit-Service-Discovery mit Lua
OpenResty dynamischer Upstream in drei Schichten: Health-Check-Vergleich, Consul/Nacos/etcd-Integrationscode für Echtzeit-Service-Discovery in Container-Umgebungen.
Teil 5 von 6
Ähnliche Beiträge
Nginx Reverse Proxy: upstream, Buffer und Timeouts im Überblick

Nginx Reverse Proxy: upstream, Buffer und Timeouts im Überblick
Nginx Performance-Tuning: gzip, Cache und Connection-Pool-Konfiguration


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen