NGINX Upstream Keepalive optimal konfigurieren für maximale Performance als Reverse Proxy

Ich konfiguriere NGINX Upstream Keepalive so, dass der Reverse Proxy weniger Verbindungen aufbaut, geringere Latenzen liefert und Lastspitzen zuverlässig abfedert. Dabei passe ich Pool‑Größe, Zeitlimits und Header gezielt an, damit Verbindungen wiederverwendet werden und der Datenpfad schlank bleibt.

Zentrale Punkte

  • HTTP/1.1 erzwingen und Connection‑Header bereinigen
  • keepalive pro Worker richtig dimensionieren
  • Timeouts an Backend‑Werte anpassen
  • Requests/Verbindung begrenzen und recyceln
  • Monitoring für Verbindungsrate und Latenz

Warum Upstream Keepalive Verbindungsaufwand drastisch senkt

Ohne Wiederverwendung eröffnet NGINX pro Request eine neue Backend‑Verbindung, was extra Handshakes, mehr CPU‑Zyklen und zusätzliche Kernel‑Ressourcen kostet; genau hier greift Keepalive ein. Ich lasse NGINX bereits aufgebaute, gerade ruhende Sockets zwischenspeichern und für Folgeanfragen nutzen, wodurch Connect‑Zeiten messbar schrumpfen. Das senkt die Verbindungsrate pro Sekunde, reduziert Backlog‑Spitzen und bremst Kontextwechsel im Betriebssystem. Besonders bei TLS zum Backend spare ich durch wiederverwendete Sessions merklich Zeit. So bleibt die Antwortkette auch bei hohem Durchsatz zuverlässig und reagiert flüssig.

Grundprinzip und die keepalive‑Direktive im Upstream

Die Direktive keepalive im upstream‑Block begrenzt die Zahl der pro Worker zwischengespeicherten, idle Backend‑Verbindungen. Dieses Limit gilt nicht global, sondern strikt je Worker‑Prozess, weshalb ich die Worker‑Anzahl immer im Blick behalte. Wenn der Pool voll ist, schließt NGINX die am längsten ungenutzte Verbindung zuerst, damit neue Sockets Platz haben. Für die Wiederverwendung benötigt die Proxy‑Seite HTTP/1.1 und einen neutralisierten Connection‑Header. Ohne diese Voraussetzungen bleibt der Pool leer, obwohl ich im Upstream „keepalive“ setze, was viele Admins anfangs überrascht.

upstream backend_pool {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    keepalive 32;              # idle Verbindungen pro Worker
    keepalive_requests 1000;   # Recycling nach N Requests
    keepalive_timeout 60s;     # Idle-Lebensdauer
}

server {
    listen 80;
    location / {
        proxy_pass http://backend_pool;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Zwingende Direktiven im Location‑Block: HTTP/1.1 und Header‑Kontrolle

Ich zwinge NGINX im Proxy‑Pfad zu HTTP/1.1, weil Keepalive mit HTTP/1.0 nicht sauber funktioniert und Verbindungen unnötig enden; die Direktive proxy_http_version 1.1 ist daher Pflicht. Zusätzlich entferne ich den Connection‑Header für reguläre Requests, damit das Backend keine „close“‑Anweisung erhält. Für Upgrades wie WebSockets setze ich per map gezielt „Connection: Upgrade“, ohne die normale Wiederverwendung zu beeinträchtigen. So bleibt die Verbindungspolitik konsistent und von Client‑Headern entkoppelt. Genau diese kleine Änderung verhindert viele schwer fassbare Fehlerbilder.

location / {
    proxy_pass http://backend_pool;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ""      "";
}

Feinsteuerung: keepalive_requests und keepalive_timeout richtig wählen

Mit zwei Stellschrauben kontrolliere ich Lebensdauer und Erneuerung der Verbindungen, damit der Pool frisch bleibt und keine verwaisten Sockets stören; das sind keepalive_requests und keepalive_timeout. Nach N Requests schließt NGINX die Leitung gezielt und baut sie bei Bedarf neu auf, was Alterungseffekte im Netz abfedert. Das Idle‑Timeout beende ich eher knapp, meist zwischen 30 und 120 Sekunden, damit Backends nicht vorher abschneiden. Wichtig ist die Abstimmung: Der NGINX‑Wert liegt nie über dem Timeout der App‑Server, sonst häufen sich Connection‑Resets. Wer Hintergründe vertiefen will, findet praxisnahe Hinweise im Beitrag Keepalive‑Timeout, der typische Werte und Wechselwirkungen erläutert.

Zur schnellen Orientierung zeige ich gängige Startwerte und den jeweiligen Zweck in einer übersichtlichen Tabelle. Die Richtwerte dienen als Startpunkt und landen nach Monitoring oft etwas höher oder niedriger. Ein zu kurzer Zeitraum erzeugt unnötige Neuaufbauten, ein zu langer hält alte Verbindungen fest. Die Anzahl Requests pro Verbindung schütze ich vor Ausreißern, ohne den Pool zu entleeren. Mit diesen Eckdaten treffe ich sehr schnell funktionierende Defaults.

Parameter Zweck Richtwert Tuning‑Hinweis
keepalive Größe des idle‑Pools pro Worker 32–64 An gleichzeitiger Last pro Worker ausrichten
keepalive_requests Max. Requests pro Verbindung 500–1000 Bei langen Streams etwas höher ansetzen
keepalive_timeout Max. Idle‑Zeit pro Verbindung 60s Kürzer oder gleich Backend‑Idle‑Timeout

Pool‑Größe anhand gleichzeitiger Verbindungen bestimmen

Ich wähle die Pool‑Größe nicht nach Requests pro Sekunde, sondern nach Concurrency pro Worker. Zuerst ermittle ich die durchschnittliche und die maximale Anzahl paralleler Backend‑Anfragen. Anschließend teile ich diese Zahlen durch die Anzahl der NGINX‑Worker und runde auf. Für 200 gleichzeitige Anfragen bei vier Workern lande ich bei rund 50 je Worker, wodurch keepalive 64 als Startwert passt. So halte ich Sockets verfügbar, ohne unnötig viele offene Verbindungen zu binden.

Besonderheiten neuerer NGINX‑Versionen bewusst nutzen

Aktuelle Versionen erlauben die Wiederverwendung oft standardmäßig, setzen jedoch eher konservative Limits; ich trage die Werte trotzdem explizit ein. Das schafft Reproduzierbarkeit, erleichtert das Tuning und verhindert Überraschungen nach einem Update. Über den Parameter „local“ grenze ich optional die Wiederverwendung auf eine Location ein, wenn Sicherheitsprofile oder Header‑Politiken abweichen. So bleibt die Trennung sauber, ohne die Vorteile der Wiederverwendung global zu verlieren. Mit klaren Werten dokumentiere ich Absichten und spare später Analysezeit.

Monitoring und Metriken: Wirkt die Konfiguration wirklich?

Ich prüfe zuerst die Zahl neuer Backend‑Verbindungen pro Sekunde; ein deutlicher Rückgang zeigt greifende Wiederverwendung. Dann beobachte ich upstream_connect_time, die bei Treffern im Pool nahe Null liegt. Fehler in Logs, speziell Connection‑Resets, deuten auf Zeitlimits hin, die hinter den Backend‑Werten liegen. Zusätzlich korreliere ich Backend‑CPU und Latenzen mit dem Anteil wiederverwendeter Verbindungen. Für tieferes Verständnis zur Connection‑Reuse helfen Beispiele, die Effekte bei verschiedenen Lastmustern zeigen.

Typische Fehlerquellen schnell ausschalten

Fehlt HTTP/1.1 zum Backend, bleiben Verbindungen kurzlebig, egal wie hoch ich keepalive setze. Wenn der Client „Connection: close“ sendet und ich den Header ungefiltert durchreiche, räumt das Backend jede Leitung direkt nach der Antwort ab. Stimmen Idle‑Timeouts nicht überein, beendet die App‑Seite die Verbindung zuerst und NGINX kassiert beim nächsten Request einen Reset. Ein überdimensionierter Pool hält zu viele Sockets offen und verschwendet Arbeitsspeicher und Ports. Ich prüfe diese vier Punkte bei jeder Analyse als Erstes, weil sie 90 % aller Probleme erklären.

Praxisbeispiel: Referenzkonfiguration für hohen Durchsatz

Mit wenigen Direktiven bringe ich einen stark belasteten Proxy in einen schnellen und verlässlichen Zustand und sichere saubere Header‑Weitergaben ab; das folgende Muster hat sich bewährt und lässt sich leicht anpassen. Ich setze keepalive 64, begrenze Requests pro Verbindung auf 1000 und halte 60 Sekunden Idle‑Zeit. Darüber hinaus reiche ich Host und Forwarded‑Informationen korrekt weiter, damit Backends Logik und Ratenbegrenzung anwenden können. Die Kombination schont CPU, verkürzt Antwortzeiten und steckt Lastspitzen gelassener weg. Genau so erreiche ich eine gut kalkulierbare Performance.

upstream app_backend {
    server 10.0.1.10:3000 max_fails=2 fail_timeout=30s;
    server 10.0.1.11:3000 max_fails=2 fail_timeout=30s;

    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
    }
}

Hosting‑Umgebungen und Betriebsaspekte, die wirklich zählen

Ich setze NGINX oft vor PHP‑FPM, Node.js oder Java‑Dienste und achte darauf, dass Netzwerk‑Latenzen gering bleiben und Backend‑Timeouts konsistent sind; das liefert Planbarkeit. Eine solide Kernel‑Netzwerkkonfiguration mit passenden Socket‑Limits verhindert, dass viele offene Verbindungen kollidieren. Gleichmäßige CPU‑Zuteilung und schnelle Storage‑Wege helfen den Backends, kurze Antwortzeiten zu halten. Zudem sorge ich für versionierte Konfigurationen, damit Änderungen nachvollziehbar bleiben. Mit dieser Disziplin bleibt das System bei Traffic‑Spitzen reagibel.

Best Practices für den laufenden Betrieb

Ich starte mit keepalive 32–64, 500–1000 Requests pro Verbindung und 60 Sekunden Idle‑Zeit, messe dann systematisch und passe die Werte an; das bringt schnelle Erfolge. Jede Änderung begleite ich mit Metriken zu Verbindungsrate, Latenz und Fehlermustern, bis die Kurven ruhiger laufen. Die Größe des Pools richte ich an gleichzeitigen Anfragen aus, nicht am Rohdurchsatz pro Sekunde. Timeouts bleiben niemals länger als die Gegenstücke im Backend‑Stack, sonst drohen sporadische Resets. Wer tiefer an der Drehzahl schrauben will, findet Hinweise zu Feintuning unter Keepalive‑Requests optimieren, was das Recycling gut beherrschbar macht.

Abgleich der Proxy‑Timeouts und TCP‑Keepalive

Neben den reinen Keepalive‑Parametern stimmen ich die Transport‑Zeitlimits präzise ab. Der Dreiklang aus proxy_connect_timeout, proxy_send_timeout und proxy_read_timeout bestimmt, wie geduldig NGINX beim Aufbau, Senden und Empfangen ist. Diese Werte lege ich nie über die Gegenstücke im Backend, sondern knapp darunter, damit Fehler früh sichtbar werden und nicht auf der App‑Seite eskalieren. Zusätzlich aktiviere ich proxy_socket_keepalive, damit das Betriebssystem in regelmäßigen Abständen Lebenszeichen über inaktive Sockets schickt und halb offene Verbindungen erkennt. Das verhindert, dass tote Leitungen im Pool liegen bleiben und beim nächsten Request für Latenzspitzen sorgen.

server {
    listen 80;

    location / {
        proxy_pass http://backend_pool;

        proxy_connect_timeout 3s;   # zügig scheitern, wenn kein Connect möglich
        proxy_send_timeout    30s;  # Schreiben zum Backend
        proxy_read_timeout    30s;  # Antworten vom Backend
        proxy_socket_keepalive on;  # OS-TCP-Keepalive aktivieren
    }
}

Für langlaufende Streams (z. B. SSE oder WebSockets) erhöhe ich ausschließlich das read‑Timeout, während Connect eng bleibt. So reagiere ich schnell auf defekte Ziele, lasse aber legitime, lange Antworten ungestört durchlaufen.

Ressourcenplanung: worker_connections, FDs und Ephemeral Ports

Ein sauberer Keepalive‑Pool bringt nichts, wenn Dateideskriptor‑Limits oder Portbereiche erschöpfen. Ich plane deshalb worker_connections und worker_rlimit_nofile mit Reserve. Grob kalkuliere ich: Offene FDs ≈ (gleichzeitige Client‑Verbindungen + gleichzeitige Backend‑Verbindungen + gepoolte Idle‑Sockets) pro Worker. Setze ich mehrere Upstreams mit Pools ein, multipliziert sich der Bedarf. Ebenso achte ich auf den Ephemeral‑Port‑Bereich des Systems, da NGINX in Richtung Backend als TCP‑Client auftritt und TIME_WAIT‑Zustände ansammelt.

worker_processes auto;
worker_rlimit_nofile 131072;

events {
    worker_connections 8192;
}
# Linux-Beispiele (sysctl):
net.core.somaxconn = 4096
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15

Ich gehe konservativ vor: TIME_WAIT nicht aggressiv wegtunen, sondern Verbindungsrate per Keepalive senken. So bleiben Kernel‑Parameter unkritisch und das Verhalten vorhersagbar.

Upstream‑Zonen, Balancer‑Strategie und DNS‑Rotation

Bei mehreren Workern teile ich Balancer‑Zustand über eine zone, damit Ausfälle und Gewichte konsistent bleiben. Keepalive‑Sockets bleiben zwar weiterhin pro Worker, aber die Verteilung wird gleichmäßiger. Bei dynamischen Backends, die über DNS umziehen, setze ich „resolve“ an den Serverzeilen und definiere einen resolver. Wichtig: Wenn IPs rotieren, recycelt der Pool nicht sofort alle alten Sockets; ich halte daher keepalive_requests und Zeitlimits im realistischen Rahmen, damit die Erneuerung zügig greift.

upstream backend_pool {
    zone backend_zone 128k;  # teilt Balancer-Zustand
    least_conn;              # faire Verteilung bei langen Requests

    server app-1.internal:8080 resolve;
    server app-2.internal:8080 resolve;

    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

resolver 10.0.0.2 valid=30s;
resolver_timeout 5s;

proxy_next_upstream error timeout http_502 http_504;
proxy_next_upstream_tries 2;  # wenige, gezielte Wiederholungen

Für Sessions, die an einen bestimmten Backend‑Knoten gebunden sind (z. B. Sticky‑State), kombiniere ich die Wiederverwendung mit ip_hash oder einem externen Session‑Mechanismus. Das verhindert, dass Connection‑Pooling Session‑Kohärenz stört.

TLS zum Backend: SNI, Session‑Wiederverwendung und Ciphers

Je stärker TLS im Backend‑Pfad genutzt wird, desto wertvoller ist Keepalive. Ich aktiviere SNI, lege den erwarteten Namen fest und sorge für Wiederverwendung der TLS‑Sitzung. Das senkt Handshake‑Kosten und glättet Latenzspitzen. Cipher‑Suite und Protokolle wähle ich restriktiv, ohne ältere Backends auszusperren. Bei Zertifikatsprüfung (optional) muss die Trust‑Kette vollständig sein, sonst fallen Verbindungen sporadisch zurück.

upstream https_backend {
    server backend.example.local:443;
    keepalive 32;
}

server {
    listen 443 ssl;

    location / {
        proxy_pass https://https_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        proxy_ssl_server_name on;
        proxy_ssl_name backend.example.local;
        proxy_ssl_session_reuse on;
        proxy_ssl_protocols TLSv1.2 TLSv1.3;
        proxy_ssl_ciphers HIGH:!aNULL:!MD5;
        # optional: proxy_ssl_verify on;
        # optional: proxy_ssl_trusted_certificate /etc/nginx/ca.pem;
    }
}

Wenn ich die Backend‑Seite selbst kontrolliere, aktiviere ich Session‑Tickets oder -Caches dort und prüfe mit Metriken, ob Resumption‑Quoten steigen. In Kombination mit Keepalive erreiche ich so dauerhaft niedrige Connect‑ und Handshake‑Zeiten.

Spezialfälle: gRPC, WebSockets und verbindungsgebundene Auth

Bei gRPC arbeitet NGINX upstream über HTTP/2. Hier bringen wenige langlebige Verbindungen mit vielen Streams oft die besten Ergebnisse; der Pool bleibt klein, aber stabil. Für WebSockets setze ich lange read‑Timeouts und belasse die Header‑Logik aus der map‑Lösung, damit Upgrade‑Verbindungen nicht versehentlich geschlossen werden. NTLM oder andere verbindungsgebundene Authentifizierungen erfordern Connection‑Pinning; ich trenne solche Pfade in eigene Locations und reduziere dort Pooling oder Wiederverwendung, damit Sicherheits‑Handshakes nicht zwischen Clients vermischt werden.

# gRPC-Beispiel
location /grpc.Service/ {
    grpc_pass grpc://backend_pool;
    grpc_read_timeout 300s;  # lange Streams zulassen
}

Entscheidend ist, pro Pfad eine konsistente Verbindungspolitik festzulegen und Keepalive nur dort breit einzusetzen, wo es semantisch unkritisch ist.

Messbarkeit in der Praxis: Access‑Logs mit Upstream‑Timings

Ich erweitere das Access‑Log um Upstream‑Metriken. So erkenne ich auf einen Blick, ob eine Antwort aus einem gepoolten Socket kam (sehr kleine Connect‑Zeit) und wie oft Backend‑Fehler auftreten. Zudem protokolliere ich die Verbindungsnummer und die Anzahl Requests über die aktuelle Client‑Verbindung, um Korrelationen zu finden.

log_format upstream_timing '$remote_addr - $host "$request" '
                           'up=$upstream_addr '
                           'sc=$status usc=$upstream_status '
                           'cc=$connection cr=$connection_requests '
                           'tc=$upstream_connect_time '
                           'th=$upstream_header_time '
                           'tr=$upstream_response_time';

access_log /var/log/nginx/access_upstream.log upstream_timing;

Zusätzlich nutze ich Status‑Endpunkte und Socket‑Statistiken des OS. Ein gesunder Zustand zeigt: sinkende Verbindungsrate zum Backend, kürzere upstream_connect_time, stabile Antwortzeiten und kaum Connection‑Resets. Abweichungen deuten fast immer auf nicht abgestimmte Zeitlimits oder zu kleine/zu große Pools hin.

Rollout‑Strategie und risikoarmes Tuning

Ich gehe iterativ vor: kleine Schritte, messen, anpassen. Zuerst Keepalive moderat aktivieren, danach Timeouts und Requests/Verbindung justieren. Änderungen spiele ich per Reload ein, ohne aktive Verbindungen zu trennen. So bleibt das Risiko gering, und Effekte sind sauber zuzuordnen.

# Änderungen validieren und ohne Downtime laden
nginx -t && nginx -s reload

Wenn ich mehrere Upstreams betreibe, tune ich sie nacheinander, beginnend mit dem kritischsten Pfad. Jede Stufe bekommt ein Beobachtungsfenster, damit sich Muster in den Metriken klar herausarbeiten. Erst danach skaliere ich Werte hoch oder runter.

Kompakte Zusammenfassung für deinen Reverse Proxy

Ich setze auf HTTP/1.1, leere den Connection‑Header und wähle die Pool‑Größe nach gleichzeitigen Anfragen, nicht nach RPS; das trägt die Leistung. Mit keepalive_requests und keepalive_timeout halte ich Verbindungen frisch und vermeide Überraschungen durch veraltete Sockets. Monitoring zeigt, ob upstream_connect_time gegen Null tendiert und ob die Verbindungsrate zum Backend sinkt. Bei Fehlern prüfe ich zuerst Protokollversion, Header‑Weitergabe, Zeitlimits und Pool‑Größe. So bleibt dein NGINX‑Proxy unter hoher Last reaktionsschnell und kalkulierbar.

Aktuelle Artikel

Moderne Server mit optimierter Redis Key Expiration Performance
Datenbanken

Redis Key Expiration Performance analysieren und optimieren

Lerne, wie du die Redis Key Expiration Performance mit passenden TTL-Strategien, Eviction-Policies und gezieltem Monitoring optimierst und deinen Cache stabil hältst. Fokus: Redis Key Expiration.