Design wechseln

Nginx Reverse Proxy: upstream, Buffer und Timeouts im Überblick

Easton editorial illustration: image-layer stack

Das Handy vibriert ununterbrochen – Alarm in der Produktion.

Im Log nur 502 Bad Gateway. Das Backend lief, aber die Timeout-Werte in der Nginx-Konfiguration waren zu knapp. Beim Traffic-Peak wurden Anfragen abgebrochen, bevor die Verarbeitung fertig war. Die Zeile proxy_read_timeout 60s war damals ein schneller Platzhalter.

Nach dem Vorfall eine Woche investiert, um die drei Kernmodule des Nginx Reverse Proxy wirklich zu verstehen: upstream-Lastverteilung, proxy buffer und Timeout-Konfiguration. Richtig eingestellt, hält der Reverse Proxy zehnfach mehr Last; falsch konfiguriert endet es wie damals um drei Uhr nachts.

Dieser Artikel bündelt Fehler, Debugging-Erfahrungen und die dahinterliegende Logik. Wer Backend oder DevOps macht – oder einfach verstehen will, was Nginx-Parameter bedeuten – spart damit Zeit.


Upstream-Lastverteilung: mehr als „Anfragen verteilen“

Grundsyntax

Der upstream-Block ist das Herz der Nginx-Lastverteilung. Die Basis kennen Sie vermutlich:

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

Einfach – Backend-Server definieren, proxy_pass zeigt dorthin. In Produktion reicht das nicht: Was passiert bei Ausfall? Wie gewichtet man stärkere Maschinen? Wie hält man Long-Living-Connections?

Vier Algorithmen, vier Szenarien

Standard ist Round-robin: der Reihe nach, fair, aber nicht intelligent.

Bei Long-Living-Connections – WebSocket, DB-Connection-Pools – kann Round-robin einzelne Server überlasten. Dann passt least_conn besser:

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

Nginx zählt aktive Verbindungen pro Server und leitet neue Anfragen an den am wenigsten belasteten. In einem WebSocket-Projekt explodierte bei Round-robin der Speicher auf einem Server – mit least_conn verteilte sich die Last deutlich gleichmäßiger.

Manchmal muss ein Nutzer nach dem Login immer denselben Server treffen (Session lokal gespeichert). Dafür gibt es ip_hash:

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

Gleiche Client-IP landet immer auf demselben Backend. Schwachstelle: fällt der Server aus, ist die Session weg. Robuster: Session in Redis, ip_hash nur als Übergang.

Viertens: konsistentes Hashing für verteilte Caches:

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

Nginx legt 160 virtuelle Knoten pro Weight-Einheit an; dieselbe URI trifft immer denselben Server – hohe Cache-Trefferquote.

Gewichtung: unterschiedliche Serverleistung

Nicht jeder Server ist gleich: 32 GB RAM und 8 Kerne neben 16 GB und 4 Kernen. Faire Round-robin-Verteilung verschwendet Kapazität.

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 erhält dreimal so viele Anfragen. Stärkere Maschinen mehr Arbeit – schwächere weniger.

Mit backup reservieren Sie einen Ersatzserver:

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

Der backup-Server nimmt erst Traffic an, wenn alle primären Server ausfallen – Ersatzbank für die Erstelf.

keepalive-Connection-Pool: der Performance-Hebel

Oft übersehen: Nginx öffnet standardmäßig pro Anfrage eine neue TCP-Verbindung zum Backend und schließt sie danach.

Drei-Wege-Handshake beim Aufbau, vier-Wege-Handshake beim Schließen – bei hoher Concurrency teuer. keepalive recycelt Verbindungen.

upstream backend {
    server 192.168.1.10:8080;
    keepalive 32;  # pro Worker bis zu 32 idle Verbindungen
}

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

Zwei Punkte:

  1. keepalive 32 – maximale idle Verbindungen pro Worker-Prozess
  2. proxy_http_version 1.1 und Connection "" sind Pflicht – HTTP/1.0 unterstützt keine persistenten Verbindungen

In einem API-Test: ohne keepalive etwa 2.000 QPS, danach 4.000+. Verdoppeln ist realistisch, kein Marketing.

QPS-Steigerung
Source: Praxiswert: nach Aktivierung des keepalive-Pools

Aber: keepalive nicht zu hoch setzen. Einmal 100 in der Testumgebung bei nur einem ECS-Container – das Backend war am Connection-Limit. Grobe Formel für Produktion:

keepalive ≈ Gesamt-QPS ÷ durchschnittliche Antwortzeit ÷ Worker-Prozesse

Beispiel: QPS 10.000, Antwortzeit 50 ms, 4 Worker:

10000 × 0,05 ÷ 4 = 125

keepalive um 125 ist ein guter Ausgangspunkt.

Health Checks: Ausfälle automatisch aussortieren

Die Open-Source-Version bietet nur passive Checks – ein Server gilt erst als ungesund, wenn Anfragen scheitern:

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 definiert Retry-Bedingungen: Verbindungsfehler, Timeout oder 502/503/504. proxy_next_upstream_tries 3 – maximal drei Server versuchen.

Passiv heißt Verzögerung: Erst ein fehlgeschlagener Request markiert den Server. Bei hohen Verfügbarkeitsanforderungen lohnt aktives Checking in NGINX Plus:

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

Alle 5 Sekunden aktive Sondierung; drei Fehlschläge → unhealthy, zwei Erfolge → wieder healthy.


Proxy Buffer: Helfer oder Störfaktor?

Wozu dient Buffering?

Nginx speichert Backend-Antworten nicht sofort beim Client, sondern erst im Buffer.

Warum? Client-Bandbreite ist unvorhersehbar. Das Backend liefert schnell, der Client empfängt langsam – ohne Buffer blockiert Nginx und hält das Backend auf. Mit Buffer nimmt Nginx die Antwort entgegen und liefert sie in eigenem Tempo; das Backend kann die nächste Anfrage bearbeiten.

Kosten: Speicher. Große Response-Bodies bei hoher Concurrency verbrauchen viel RAM.

Drei Kernparameter und ihr Zusammenspiel

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

Am Anfang verwirrend – ähnliche Namen, unterschiedliche Rollen:

  • proxy_buffer_size: Buffer für Response-Header, einer pro Request
  • proxy_buffers: Array für den Response-Body – Format Anzahl Größe
  • proxy_busy_buffers_size: gerade an den Client gesendeter Anteil; maximal die Hälfte der Gesamt-buffers

Beispiel: proxy_buffers 8 32k → 8 × 32k = 256k gesamt. proxy_busy_buffers_size 64k ist ein Viertel – regelkonform.

Wann anpassen?

Sehr große Response-Header (viele Cookies) → Fehler „upstream sent too big header“. Lösung: proxy_buffer_size erhöhen:

proxy_buffer_size 16k;

Große Response-Bodies (lange JSON-Antworten):

proxy_buffers 16 64k;

Spezialfälle: Buffering abschalten

Manchmal stört Buffering.

Server-Sent Events (SSE): kontinuierlicher Event-Stream – mit Buffering kommen Nachrichten verzögert an:

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

proxy_read_timeout 86400s (ein Tag), weil SSE Long-Living ist und nicht durch Timeout getrennt werden darf.

WebSocket – bidirektionale Echtzeitkommunikation:

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

Große Uploads: 1 GB hochladen und erst komplett puffern, dann weiterleiten – Speicherexplosion. Request-Buffering aus:

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

proxy_request_buffering off streamt beim Empfang direkt weiter.


Timeouts: die Logik hinter den Parametern

Drei Timeouts, drei Aufgaben

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

Ähnliche Namen, klare Trennung:

  • proxy_connect_timeout: Wartezeit auf TCP-Verbindung zum Backend (Netzwerk, Firewall)
  • proxy_read_timeout: nach Verbindungsaufbau – maximales Intervall zwischen zwei Leseoperationen vom Backend
  • proxy_send_timeout: Zeitlimit zum Senden des Request-Bodies ans Backend

Häufige Verwechslung: proxy_read_timeout ist keine Gesamtzeit, sondern das Intervall zwischen Reads. Verarbeitet das Backend fünf Minuten, sendet aber Heartbeats, reichen 60s. Bleibt es fünf Minuten stumm, brauchen Sie proxy_read_timeout 300s.

Timeouts und 502/504

Die Lektion vom Alarm um drei Uhr nachts:

  • 502 Bad Gateway: Nginx erreicht das Backend nicht – Dienst down, Port blockiert, Firewall
  • 504 Gateway Timeout: Verbindung steht, Backend antwortet zu langsam

Beispiel: proxy_connect_timeout 10s, Backend antwortet erst nach 15s auf Connect → 502. Connect schnell, Verarbeitung zwei Minuten, proxy_read_timeout 60s → 504.

Timeout-Strategien je Szenario

API-Dienste: 30–60 Sekunden reichen meist. Kurze Timeouts decken langsame Requests schneller auf:

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

Dateiverarbeitung: Reports, PDF-Generierung – Minuten statt Sekunden:

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

Streaming: Live-Video, WebSocket, SSE – Long-Living, ein Tag ist normal:

proxy_read_timeout 86400s;

502/504 in der Praxis debuggen

Typische Ursachen

Aus eigener Erfahrung:

  1. Backend wirklich down: Prozess abgestürzt, Port belegt, OOM
  2. Connection-Limit erreicht: Backend-Pool voll, Nginx kommt nicht durch
  3. Timeout zu kurz: wie beim Nachtalarm – proxy_read_timeout 60s, Backend braucht zwei Minuten
  4. Firewall/Netzwerk: Security Group, iptables blockieren

Log-Analyse

Immer zuerst error_log:

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

Typische Meldungen:

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

→ 504, Lese-Timeout.

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

→ 502, Verbindung abgelehnt – Backend lauscht nicht.

Fortgeschritten: eigenes Log-Format mit upstream-Status:

log_format upstream_status '$status $upstream_status $upstream_response_time';

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

Ausgabe wie 200 200, 200, 502 0.5, 1.2, 3.0 – Status pro Backend und Laufzeit auf einen Blick.

Typische Lösungen

Szenario 1: langsames Backend, häufig 504

proxy_read_timeout erhöhen – und prüfen, ob das Backend wirklich fertig wird. Nginx allein reicht nicht; Backend-Timeouts synchron anpassen.

Szenario 2: Connection refused, 502

Prozess läuft? Port offen? Firewall-Regeln?

netstat -tlnp | grep 8080
ps aux | grep your_app

Szenario 3: Connection-Limit unter Last

Backend-Pool vergrößern oder upstream keepalive aktivieren, um neue Verbindungen zu sparen.


Performance-Best-Practices

Worker-Konfiguration

Nginx ist multi-prozessfähig. worker_processes – üblicherweise Anzahl CPU-Kerne:

worker_processes auto;

auto erkennt Kerne automatisch. Acht Kerne → acht Worker.

worker_connections – maximale Verbindungen pro Worker:

events {
    worker_connections 4096;
}

Theoretisches Maximum: worker_processes × worker_connections. 8 × 4096 = 32.768 – in der Praxis begrenzt durch Dateideskriptoren.

TCP-Optimierung: das Dreierpaket

sendfile on;
tcp_nopush on;
tcp_nodelay on;

Kombiniert spürbar schneller:

  • sendfile on: Kernel-Transfer, umgeht Userspace-Buffer
  • tcp_nopush on: mit sendfile Pakete bündeln statt einzeln senden
  • tcp_nodelay on: kleine Pakete sofort, ohne auf Buffer-Füllung zu warten

Bei statischen Dateien messbar 30 %+ Durchsatzsteigerung.

30 %+
Durchsatzsteigerung
Source: Praxiswert: sendfile + tcp_nopush + tcp_nodelay aktiv

Weitere Optimierungen

gzip-Kompression: Textantworten komprimieren, Bandbreite sparen:

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

Dateideskriptor-Limit: unter hoher Last oft zu niedrig:

ulimit -n

Nur 1024? In /etc/security/limits.conf anheben:

* soft nofile 65535
* hard nofile 65535

Vollständiges Konfigurationsbeispiel

Empfohlene Produktionsvorlage:

# Basis
worker_processes auto;

events {
    worker_connections 4096;
    multi_accept on;
}

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

    # Keepalive
    keepalive_timeout 30;
    keepalive_requests 100;

    # Buffer
    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;
        }
    }
}

Fazit

Kernpunkt in drei Sätzen:

  1. upstream: passenden Algorithmus wählen, keepalive aktivieren, Health Checks einplanen
  2. Buffer: drei Parameter verstehen; bei SSE/WebSocket/Uploads Buffering abschalten
  3. Timeouts: drei Parameter unterscheiden; Szenario bestimmt die Werte

Der Nachtalarm lehrte: Nginx-Konfiguration ist kein Parameter-Ausfüllen. Jeder Wert hat Design-Hintergrund – wer die Logik versteht, vermeidet die typischen Fallen.

Neu bei Nginx? Mit Defaults starten und gezielt nachjustieren – nicht wie damals proxy_read_timeout 60s blind in Produktion. Schon Erfahrung gesammelt? Dieser Artikel ordnet lose Erfahrungen zu einem System.

Beim Abschluss kurz in die Produktionskonfiguration geschaut: keepalive 32, proxy_read_timeout 120s, least_conn. Der Alarm um drei Uhr nachts ist nicht wiedergekommen.


FAQ

Ist proxy_read_timeout eine Gesamtzeit oder ein Intervall zwischen Leseoperationen?
Das Intervall zwischen zwei Leseoperationen. Sendet das Backend während der Verarbeitung kontinuierlich Daten (z. B. Heartbeats), reicht proxy_read_timeout 60s auch bei fünf Minuten Gesamtlaufzeit. Bleibt das Backend fünf Minuten völlig stumm, muss der Wert auf 300s stehen.
Wann sollte man proxy_buffering abschalten?
In drei Szenarien zwingend:

• Server-Sent Events (SSE): Echtzeit-Push – Buffering verzögert Nachrichten
• WebSocket: bidirektionale Echtzeitkommunikation, Streaming erforderlich
• große Uploads: Speicher sparen, Daten beim Empfang direkt weiterleiten
Welcher keepalive-Wert ist sinnvoll?
Formel: keepalive ≈ Gesamt-QPS × durchschnittliche Antwortzeit ÷ Worker-Prozesse. Beispiel: QPS 10.000, Antwortzeit 50 ms, 4 Worker → keepalive etwa 125. Nicht zu hoch setzen – einmal 100 in der Testumgebung, und das einzelne Backend-ECS war am Connection-Limit.
Was ist der Unterschied zwischen 502 und 504?
502 Bad Gateway: Nginx erreicht das Backend nicht (Dienst down, Port blockiert, Firewall). 504 Gateway Timeout: Verbindung steht, aber Antwort kommt zu spät (langsames Backend). Diagnose unterscheidet sich: bei 502 Prozess und Port prüfen, bei 504 Timeout-Konfiguration und Backend-Laufzeit.
Welchen Lastverteilungsalgorithmus wählen?
Je nach Szenario:

• Round-robin (Default): zustandslose Dienste, faire Verteilung
• least_conn: Long-Living-Connections (WebSocket, DB-Connection-Pool)
• ip_hash: Session-Affinität (Übergangslösung – Redis ist robuster)
• hash: verteilter Cache, höhere Trefferquote
Wie behebt man „upstream sent too big header“?
proxy_buffer_size erhöhen. Zu große Response-Header (z. B. viele Cookies) überschreiten den Default von 4k. proxy_buffer_size 16k löst das meist.
Warum verbessern sendfile + tcp_nopush + tcp_nodelay die Performance?
sendfile überträgt im Kernel und umgeht den Userspace; tcp_nopush bündelt Pakete; tcp_nodelay sendet kleine Daten sofort. Kombiniert steigt der Durchsatz bei statischen Dateien messbar um 30 %+.

8 Min. Lesezeit · Veröffentlicht am: 30. März 2026 · Aktualisiert am: 14. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog