Design wechseln

Nginx Performance-Tuning: gzip, Cache und Connection-Pool-Konfiguration

Easton editorial illustration: service mesh rail yard

Letzte Woche kam der Alarm: Die Startseite eines E-Commerce-Shops lud plötzlich in 4 Sekunden. Chrome DevTools geöffnet – HTML 120 KB, CSS plus JS weitere 350 KB, alles unkomprimiert. Schlimmer noch: Jeder Request ging ans Backend, Cache-Hit-Rate nur 12 %. Abends nach Feierabend 2 Stunden Nginx-Konfiguration angepasst: gzip aktiviert, Cache-Strategie ergänzt, Connection-Pool-Parameter optimiert. Am nächsten Morgen: Startseiten-Ladezeit auf 1,6 Sekunden, Backend-QPS fast halbiert.

Solche Probleme sind allzu häufig. Viele installieren Nginx und lassen es laufen – gzip standardmäßig aus, Cache nur halbherzig konfiguriert, Verbindungslimit auf Default. Bei Traffic-Spitzen gerät der Server außer Atem.

Dieser Artikel fasst die in der Produktion validierten Nginx Performance-Tuning-Konfigurationen zusammen. gzip reduziert das Übertragungsvolumen um 60–80 %, bei 95 % Cache-Hit-Rate sinkt die Backend-Last um 90 %, ein gut konfigurierter Connection-Pool vervier- bis verfünffacht die Parallelität. Konfigurationsdetails, Fallstricke und Messdaten werden ausführlich erklärt.

Kapitel 1: gzip-Komprimierung – Übertragungsvolumen reduzieren

Warum gzip so wichtig ist: Stellen Sie sich vor, Ihre HTML-Datei ist 100 KB groß – per gzip komprimiert sind es nur noch 20–25 KB. Die gesparten 75–80 KB Bandbreite bedeuten schnellere Ladezeiten für Nutzer und geringere Traffic-Kosten für Sie.

Bei einem Kundenprojekt wurde mir das bewusst: Über 60 % mobile Nutzer, viele im 4G-Netz. Startseite 3–4 Sekunden Ladezeit, Absprungrate 70 %. Mit gzip sank das Übertragungsvolumen um 70 %, First Paint auf ca. 1,5 Sekunden.

1.1 Grundkonfiguration: Erst zum Laufen bringen

Die gzip-Konfiguration in Nginx ist überschaubar:

http {
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

gzip on ist der Schalter. gzip_vary on ist entscheidend: Der Response-Header Vary: Accept-Encoding teilt CDN und Browser mit, dass sich der Inhalt je nach Komprimierungsfähigkeit des Clients unterscheidet – verhindert Cache-Fehler.

gzip_min_length 1000 bedeutet: Dateien unter 1 KB werden nicht komprimiert. Zu kleine Dateien bringen wenig Nutzen und kosten CPU. gzip_types legt die MIME-Typen fest; standardmäßig wird nur text/html komprimiert – CSS, JS, JSON, XML müssen explizit ergänzt werden.

1.2 Fortgeschritten: Kompressionslevel und MIME-Typen

Das Kompressionslevel erfordert Abwägung. gzip_comp_level reicht von 1–9: höhere Werte = bessere Kompression, mehr CPU-Verbrauch.

Testergebnisse:

KompressionslevelHTML-KompressionCPU-Zeit (ms)Empfohlenes Szenario
165 %2Knapp CPU
472 %3Ausgewogen (empfohlen)
675 %5Knapp Bandbreite (empfohlen)
978 %12Extremszenario

Level 4 und 6 sind in den meisten Fällen die beste Wahl. Level 9 verdoppelt den CPU-Verbrauch bei nur wenigen Prozentpunkten mehr Kompression – lohnt sich nicht.

Vollständige gzip-Konfiguration aus der Produktion:

# gzip-Komprimierung
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml
    application/xml+rss
    application/xhtml+xml
    application/x-javascript;
gzip_disable "msie6";

gzip_proxied any wird oft übersehen. Wenn Nginx als Reverse Proxy arbeitet und das Backend keinen Content-Length-Header liefert, wird standardmäßig nicht komprimiert. Mit any werden alle passenden Responses komprimiert.

gzip_disable "msie6" dient der Kompatibilität mit altem IE6, der gzip fehlerhaft unterstützt. IE6 ist praktisch ausgestorben – die Zeile kann entfernt werden, ich lasse sie aus Vorsicht drin.

1.3 Welche Dateitypen profitieren am meisten?

Messdaten zeigen deutliche Unterschiede:

DateitypOriginalgrößeNach KompressionKompressionsrate
HTML100 KB20–25 KB75–80 %
CSS80 KB24–28 KB65–70 %
JavaScript120 KB36–42 KB65–70 %
JSON API50 KB20–25 KB50–60 %
Bild/Videobereits komprimiertsinnlos0–5 %
75–80 %
HTML-Datei-Kompressionsrate
Source: Produktions-Messdaten

Bilder und Videos sind bereits komprimiert (JPEG, PNG, MP4) – erneutes gzip kann das Volumen sogar vergrößern. image/* und video/* gehören nicht in gzip_types.

Einmal half ich bei der Fehlersuche: In gzip_types stand image/jpeg – Bilder wurden 3–5 % größer. Solche Anfängerfehler habe ich selbst gemacht, als ich noch nicht wusste, dass nicht jeder MIME-Typ Sinn macht.

Kapitel 2: Cache-Strategie – statische Inhalte beschleunigen

Cache ist der direkteste Hebel beim Performance-Tuning. Richtig konfiguriert liefert Nginx 95 % der Requests direkt aus – ohne Backend-Anfrage. Zu oft läuft das Backend auf Hochtouren, während der Nginx-Cache ungenutzt bleibt – nicht weil er aus ist, sondern weil er falsch konfiguriert ist.

2.1 proxy_cache oder fastcgi_cache?

Nginx bietet zwei Cache-Mechanismen:

  • proxy_cache: Cached Responses von Upstream-Servern – für Reverse-Proxy-Szenarien (Node.js, Python, Go)
  • fastcgi_cache: Cached FastCGI-Prozess-Responses – für PHP-FPM

Die Wahl hängt vom Backend-Stack ab: PHP → fastcgi_cache; Node.js, Python, Go → proxy_cache. Die Konfigurationslogik ist nahezu identisch – im Folgenden am Beispiel von proxy_cache.

2.2 Vollständige proxy_cache-Konfiguration

Zuerst im http-Block den Cache-Pfad definieren:

http {
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;
}

Zeile für Zeile:

  • levels=1:2: Verzeichnisstruktur mit zwei Ebenen – vermeidet zu viele Dateien in einem Ordner
  • keys_zone=my_cache:10m: Cache-Name und Speicher für Metadaten – 10m reicht für ca. 80.000 Cache-Keys
  • max_size=10g: Obergrenze der Cache-Größe – Überschuss wird per LRU entfernt
  • inactive=60m: Einträge ohne Zugriff in 60 Minuten werden gelöscht
  • use_temp_path=off: Direktes Schreiben ins Cache-Verzeichnis – kein Temp-Datei-Overhead

Dann im server- oder location-Block aktivieren:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_cache my_cache;

        # Cache-Gültigkeit
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_valid any 1m;

        # Cache-Key-Design
        proxy_cache_key $scheme$request_method$host$request_uri;

        # Stale-Strategie (siehe unten)
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

        # Cache-Status im Response-Header (Debug)
        add_header X-Cache-Status $upstream_cache_status;
    }
}

proxy_cache_valid definiert die Cache-Dauer je Statuscode:

  • 200 302 10m: Erfolgreiche Responses 10 Minuten
  • 404 1m: 404-Fehler 1 Minute – schützt Backend vor Missbrauch
  • any 1m: Andere Statuscodes 1 Minute

2.3 Cache-Key-Design und Invalidierung

Der Cache-Key proxy_cache_key bestimmt, wann zwei Requests als „gleich“ gelten. Default ist $scheme$proxy_host$request_uri – explizite Deklaration ist empfehlenswert:

proxy_cache_key $scheme$request_method$host$request_uri;

Der Key enthält Protokoll, HTTP-Methode, Hostname und vollständige URI – präziser bei GET/POST-Mix oder mehreren Domains.

Invalidierung ist knifflig. Gängige Strategien:

  1. Zeitbasiert: proxy_cache_valid – automatisches Ablaufen
  2. Aktives Bypass: proxy_cache_bypass umgeht den Cache
  3. Purge: Nginx Plus bietet proxy_cache_purge

Ich nutze meist Variante 2 über Request-Header:

# Bestimmter Header umgeht Cache
proxy_cache_bypass $http_x_nocache;

# Oder Query-Parameter
proxy_cache_bypass $arg_nocache;

Cache refreshen: ?nocache=1 anhängen oder Header X-Nocache: 1 senden.

2.4 Stale-Strategie: Dienst auch bei Backend-Ausfall

proxy_cache_use_stale ist sehr praktisch: Bei Backend-Fehler oder Timeout liefert Nginx abgelaufene Cache-Inhalte statt eines Fehlers.

proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

Beim letzten Singles’ Day hatten wir Backend-Probleme beim Skalieren – API intermittierend 502. Dank Stale-Strategie merkten Nutzer kaum etwas – Inhalt war ein paar Minuten alt, aber verfügbar. Nach Backend-Recovery aktualisierte sich der Cache automatisch.

Messdaten-Vergleich:

MetrikOhne CacheCache-HitStale-Modus
Antwortzeit150–200 ms5–10 ms5–10 ms
Backend-QPS1000500
NutzererlebnisNormalNormalEtwas langsamer
95 %
Cache-Hit-Rate
Source: Produktions-Messdaten

Bei Cache-Hit sinkt die Antwortzeit von 200 ms auf 5–10 ms – fast 20-fach schneller. Der Nutzen ist unmittelbar sichtbar.

Kapitel 3: Connection-Pool – unverzichtbar bei hoher Parallelität

gzip und Cache lösen „schneller übertragen“ – der Connection-Pool löst „mehr Requests tragen“. Mit Default-Einstellungen hat ein Nginx-Worker maximal 1024 Verbindungen – bei Traffic-Spitzen reicht das nicht.

3.1 worker_connections: Obergrenze berechnen

Formel für maximale parallele Verbindungen:

Max. Parallelität = worker_processes × worker_connections

Beispiel: 8 CPU-Kerne, worker_processes auf 8 (oder auto), worker_connections auf 4096:

Max. Parallelität = 8 × 4096 = 32768

Die Zahl wirkt groß – aber jeder Request bindet typischerweise zwei Verbindungen (Client→Nginx, Nginx→Backend). Tatsächlich verarbeitbare parallele Requests: etwa die Hälfte.

Konfiguration im events-Block:

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

use epoll ist unter Linux Default – explizit setzen schadet nicht. multi_accept on lässt Worker mehrere neue Verbindungen gleichzeitig annehmen – reduziert Warteschlangen bei hoher Last.

3.2 Client-Keepalive: Verbindungen wiederverwenden

TCP-Verbindungsaufbau kostet Zeit (Three-Way-Handshake). Keepalive erlaubt Wiederverwendung zwischen Client und Nginx.

http {
    keepalive_timeout 65;
    keepalive_requests 1000;
}

keepalive_timeout 65: Verbindung bleibt 65 Sekunden offen. Zu lang bindet Ressourcen, zu kurz schwächt den Wiederverwendungseffekt. 60–75 Sekunden ist ein sinnvoller Bereich.

keepalive_requests 1000: Eine Verbindung verarbeitet maximal 1000 Requests. Zu niedrig = häufiges Trennen, zu hoch = Risiko für Ressourcenlecks. 1000 hat sich in Tests bewährt.

3.3 upstream keepalive: Backend-Connection-Pool

Viele kennen diese Konfiguration nicht – der Effekt ist deutlich. Nginx kann auch Verbindungen zum Backend wiederverwenden und spart TCP-Aufbau.

upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;

    keepalive 64;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

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

keepalive 64: 64 idle Verbindungen im Pool – typisch 4–8× Anzahl der Backend-Server.

proxy_http_version 1.1 und proxy_set_header Connection "" sind Pflicht. HTTP/1.1 unterstützt keepalive; leerer Connection-Header ermöglicht Wiederverwendung. Ohne diese Zeilen greift upstream keepalive nicht.

Messdaten-Vergleich:

KonfigurationVerbindungsaufbauten/Min.CPU-OverheadEmpfohlenes Szenario
Ohne upstream keepalive6000HochNiedriger Traffic
keepalive 323000MittelMittlerer Traffic
keepalive 641500NiedrigHoher Traffic

Mit upstream keepalive sinken Verbindungsaufbauten um 50 % – besonders wichtig bei hoher Parallelität.

3.4 Parameter-Referenz nach Szenario

Empfohlene Werte je Traffic-Stufe:

ParameterNiedriger Traffic (<1000 QPS)Mittlerer Traffic (1000–5000 QPS)Hoher Traffic (>5000 QPS)
worker_processesautoautoauto
worker_connections102420484096
keepalive_timeout606575
keepalive_requests1005001000
upstream keepalive163264

Das ist der Ausgangspunkt – Feintuning braucht Lasttests. Ich nutze wrk oder ab und beobachte Verbindungszahlen und Antwortzeit-Kurven.

Kapitel 4: Gesamtvorlage – produktionsreif

Die ersten drei Kapitel erklären die Prinzipien – hier eine integrierte Vorlage. Parameter anpassen, Grundgerüst ist universell.

# nginx.conf – Produktionsvorlage

user nginx;
worker_processes auto;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # gzip-Komprimierung
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1000;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript application/xml
               application/xml+rss application/xhtml+xml;
    gzip_disable "msie6";

    # Cache-Pfad
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;

    # Client-Verbindungen
    keepalive_timeout 65;
    keepalive_requests 1000;

    # Backend-Servergruppe
    upstream backend {
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
        keepalive 64;
        keepalive_timeout 60s;
        keepalive_requests 1000;
    }

    server {
        listen 80;
        server_name example.com;

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

            # Cache
            proxy_cache my_cache;
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_key $scheme$request_method$host$request_uri;
            proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

            # Debug-Header
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

Differenzierte Konfiguration nach Szenario

E-Commerce: Startseite und Produktdetails ändern sich häufig – Cache 10–15 Minuten. upstream keepalive groß wählen, da DB-Queries das Backend belasten.

API-Dienste: Hohe Aktualität – proxy_cache_valid nur 1–5 Minuten. gzip wirkt gut auf JSON – unbedingt aktivieren.

Statische Sites: HTML, CSS, JS ändern sich selten – Cache 1 Stunde oder länger. gzip bringt hier den größten Nutzen.

Kapitel 5: Häufige Probleme und Fehlersuche

Typische Probleme aus der Praxis – direkt mit Lösung.

F: gzip greift nicht, Response-Header ohne Content-Encoding: gzip

Drei Stellen prüfen:

  1. gzip on im richtigen Block (http)
  2. gzip_types enthält den Response-MIME-Typ
  3. Response-Größe über gzip_min_length

Mit curl testen: curl -H "Accept-Encoding: gzip" -I http://your-site.com

F: Niedrige Cache-Hit-Rate, X-Cache-Status meist MISS

Häufige Ursachen:

  • Cache-Key ungünstig – jeder Request gilt als „anders“
  • proxy_cache_valid zu kurz
  • Backend liefert Cache-Control: no-cache oder Set-Cookie

Response-Header prüfen, ob Caching verboten wird.

F: worker_connections reicht nicht, 502-Fehler

Nginx-Fehlerlog prüfen – bei worker_connections are not enough ist die Parallelität überschritten.

Lösungen:

  1. worker_connections erhöhen
  2. Connection-Leaks prüfen (Keepalive-Einstellungen)
  3. Lastverteilung auf mehrere Server

F: Hoher Speicherverbrauch, häufiges OOM

Mögliche Ursachen:

  • keys_zone in proxy_cache_path zu groß
  • Zu viele Cache-Dateien, hoher Memory-Mapping-Verbrauch
  • Keepalive-Pool zu groß, viele idle Verbindungen

Parameter reduzieren oder mehr RAM bereitstellen.

Abschluss

Die drei Säulen des Nginx Performance-Tunings: gzip-Komprimierung, Cache-Strategie, Connection-Pool. Richtig kombiniert verdoppeln Sie die Ladegeschwindigkeit und vervier- bis verfünffachen die Parallelität.

Tuning ist kein Einmal-Projekt. Empfohlene Reihenfolge:

  1. gzip zuerst: Minimaler Aufwand, maximaler Nutzen – in zehn Minuten erledigt
  2. Cache danach: Nach Geschäftsszenario – innerhalb eines Tages umsetzbar
  3. Connection-Pool zuletzt: Lasttest nötig – wenn Traffic stabil ist

Nach jeder Änderung Lasttest: wrk oder ab, Antwortzeit, QPS, Fehlerrate beobachten. Nicht nach Gefühl, sondern nach Daten.

Checkliste zum Abhaken:

  • gzip aktiv, MIME-Typen vollständig
  • gzip_comp_level 4–6, CPU und Kompression ausbalanciert
  • proxy_cache_path konfiguriert, Cache-Größe sinnvoll
  • proxy_cache_valid nach Geschäftsszenario
  • proxy_cache_use_stale Stale-Strategie aktiv
  • worker_connections 4096 oder höher
  • keepalive_timeout 60–75 Sekunden
  • upstream keepalive inkl. HTTP/1.1 und Connection-Header
  • X-Cache-Status-Header für Debugging

Das war’s. Bei Fragen gerne in den Kommentaren – ich antworte, wenn ich kann.

Nginx Performance-Tuning-Konfigurationsablauf

Drei Schritte für gzip-Komprimierung, Cache-Strategie und Connection-Pool-Optimierung in der Produktion

⏱️ Estimated time: 30 min

  1. 1

    Step 1: gzip-Komprimierung aktivieren

    Im http-Block konfigurieren:

    • gzip on; Komprimierung aktivieren
    • gzip_vary on; Vary-Response-Header setzen
    • gzip_comp_level 6; Kompressionslevel festlegen (empfohlen 4–6)
    • gzip_min_length 1000; Dateien unter 1 KB nicht komprimieren
    • gzip_types MIME-Typen angeben: text/plain text/css application/json application/javascript
  2. 2

    Step 2: proxy_cache konfigurieren

    Zweistufige Konfiguration:

    Schritt 1: Cache-Pfad definieren
    • proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m

    Schritt 2: Cache im location-Block aktivieren
    • proxy_cache my_cache;
    • proxy_cache_valid 200 10m; Erfolgreiche Responses 10 Minuten cachen
    • proxy_cache_use_stale error timeout http_502; Stale-Strategie konfigurieren
  3. 3

    Step 3: Connection-Pool-Parameter optimieren

    Drei zentrale Einstellungen:

    • worker_connections 4096; im events-Block setzen
    • keepalive_timeout 65; keepalive_requests 1000; im http-Block setzen
    • upstream keepalive 64; proxy_http_version 1.1; proxy_set_header Connection ""; Backend-Connection-Pool

FAQ

Welches gzip-Kompressionslevel ist sinnvoll?
Empfohlen: Level 4–6. Level 4 erreicht 72 % Kompression bei geringem CPU-Verbrauch – geeignet bei knapper CPU. Level 6 erreicht 75 % Kompression und spart mehr Bandbreite – geeignet bei knapper Bandbreite. Level 9 verdoppelt den CPU-Verbrauch bei minimalem Zugewinn – nicht empfohlen.
proxy_cache oder fastcgi_cache – wie wählen?
Abhängig vom Backend-Stack: Node.js, Python, Go und andere Application Server nutzen proxy_cache; PHP-FPM nutzt fastcgi_cache. Die Konfigurationslogik ist nahezu identisch – Kernparameter sind Cache-Pfad, Gültigkeitsdauer und Cache-Key-Design.
Welchen Wert soll worker_connections haben?
Je nach CPU-Kernen und Traffic-Schätzung: niedriger Traffic (<1000 QPS) 1024; mittlerer Traffic (1000–5000 QPS) 2048; hoher Traffic (>5000 QPS) 4096. Tatsächliche Parallelität = worker_processes × worker_connections ÷ 2.
Niedrige Cache-Hit-Rate – wie debuggen?
Drei Schritte: 1. Cache-Key prüfen – jeder Request sollte nicht als „anders“ gelten; 2. proxy_cache_valid-Dauer prüfen – evtl. zu kurz; 3. Backend-Response-Header prüfen auf Cache-Control: no-cache oder Set-Cookie, die Caching verbieten.
Warum muss upstream keepalive HTTP/1.1 nutzen?
HTTP/1.0 unterstützt keepalive standardmäßig nicht – HTTP/1.1 ist nötig, um Backend-Verbindungen wiederzuverwenden. Zusätzlich proxy_set_header Connection "" setzen, sonst schließt Nginx die Verbindung und upstream keepalive greift nicht.
Wie Cache-Zeiten für verschiedene Geschäftsszenarien setzen?
E-Commerce-Startseite und Produktdetailseiten ändern sich häufig – Cache 10–15 Minuten. API-Dienste brauchen hohe Aktualität – Cache 1–5 Minuten. Statische Sites (HTML/CSS/JS) ändern sich selten – Cache 1+ Stunde. Grundregel: Je schneller sich das Geschäft ändert, desto kürzer die Cache-Zeit.

9 Min. Lesezeit · Veröffentlicht am: 11. Apr. 2026 · Aktualisiert am: 14. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog