Web Server

Nginx

Nginx ist der event-gesteuerte Webserver, der vor fast jeder Node, Python und Go App in der Produktion sitzt. Er terminiert TLS, liefert statische Dateien aus, verteilt die Last und schützt Ihren Prozess vor dem Internet.

intermediate15 min readUpdated 16. Sept. 2026
sites-available/app
nginx
# /etc/nginx/sites-available/app
server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

  location / {
    proxy_pass http://127.0.0.1:3000;
    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;
  }
}
Veröffentlicht
2004
Modell
Event-gesteuert, async
Hauptrollen
Web server, reverse proxy, load balancer
Konfigurationssprache
Deklarative Blöcke
TLS
Terminiert bei Nginx
Typische Upstreams
Node, Python, Go, PHP

Warum es wichtig ist

Was Nginx Ihrer Anwendung abnimmt

Ein Einstiegspunkt für alles

Eine einzige öffentliche Adresse steht vor vielen Diensten. Routen Sie per Hostname oder Pfad zu verschiedenen Apps und verschieben Sie diese, ohne DNS oder Client-Code ändern zu müssen.

TLS und Sicherheit an der Edge

Nginx terminiert HTTPS, leitet einfaches HTTP weiter, fügt Security-Header hinzu und limitiert missbräuchlichen Traffic per Rate Limiting, bevor er überhaupt Ihren Prozess erreicht.

Schnelle Auslieferung statischer Dateien und Caching

Statische Dateien und gecachte Antworten werden mittels sendfile und Kernel-Caching direkt von der Festplatte ausgeliefert, was weitaus effizienter ist, als Node damit zu beauftragen.

Das Gesamtbild

Die drei Hauptaufgaben von Nginx

Er nimmt Verbindungen aus dem Internet an, entscheidet, was mit jeder Anfrage zu tun ist, und spricht TLS, damit Ihre App es nicht tun muss.

Der Listener

Accept

Nginx bindet die Ports, führt den TLS-Handshake durch und liest die Anfrage. Ihre App sieht niemals den rohen Socket.

Der Router

Match

server_name wählt den virtuellen Host aus, dann matchen location-Blöcke den Pfad und entscheiden, ob ausgeliefert, proxied, gecacht oder abgelehnt wird.

Der Upstream

Forward

proxy_pass reicht die Anfrage an Ihre Anwendung weiter, fügt Forwarding-Header hinzu und streamt die Antwort zurück an den Client.

Nginx auf einen Blick

Die Direktiven, die Sie häufig nutzen werden

server blocks

Einer pro Seite oder Hostname, ausgewählt durch server_name und Port.

location blocks

Matchen ein Pfad-Präfix oder einen Regex und legen die Handhabung fest.

proxy_pass

Leitet eine Anfrage an eine Upstream-App oder einen anderen Server weiter.

TLS

Zertifikate, Protokolle und Cipher-Einstellungen befinden sich im server-Block.

upstream

Ein Pool von Backends mit einer Balancing-Methode und Health-Checks.

limit_req & proxy_cache

Rate Limiting für Missbrauch und Caching von Antworten, um die Last am Origin zu senken.

Ablauf

Eine Anfrage durch den Reverse Proxy

Jede Anfrage folgt demselben Pfad, egal ob sie bei einer statischen Datei oder bei Ihrer Anwendung endet.

  1. 1

    Der Client verbindet sich

    Ein Browser öffnet eine TCP-Verbindung zu Port 443 der öffentlichen Adresse des Servers und startet einen TLS-Handshake.

  2. 2

    Nginx terminiert TLS

    Nginx präsentiert das Zertifikat und entschlüsselt die Anfrage, sodass die Upstream-Anwendung nur mit einfachem HTTP arbeitet.

  3. 3

    Der virtuelle Host wird gewählt

    Nginx gleicht den Host-Header mit server_name ab, um den richtigen server-Block für diese Domain auszuwählen.

  4. 4

    Ein Location wird gematcht

    Der Pfad der Anfrage wird nacheinander mit location-Blöcken abgeglichen; der spezifischste Match bestimmt den Handler.

  5. 5

    Die Anfrage wird bedient oder proxied

    Nginx gibt eine statische Datei zurück oder leitet die Anfrage an den durch proxy_pass definierten Upstream weiter.

  6. 6

    Forwarding-Header werden hinzugefügt

    Host, X-Real-IP, X-Forwarded-For und X-Forwarded-Proto teilen der App den echten Client und das Schema mit.

  7. 7

    Caching und Limits greifen

    proxy_cache kann die Anfrage direkt von der Festplatte bedienen, und limit_req kann sie ablehnen, bevor etwas den Upstream erreicht.

  8. 8

    Die Antwort kehrt zurück

    Nginx streamt die Upstream-Antwort zurück an den Client, wobei optional Komprimierung erfolgt und Header hinzugefügt werden.

Der vollständige Leitfaden

Nginx: Alles was Sie wissen müssen

Was Nginx eigentlich ist

Nginx (ausgesprochen “engine-x”) ist ein Webserver, der auf einer ereignisgesteuerten, asynchronen Architektur basiert. Anstatt einen Thread oder Prozess pro Verbindung zu nutzen, verarbeiten wenige Worker-Prozesse tausende gleichzeitige Verbindungen, indem sie auf Events reagieren. Dank dieses Designs kann Nginx vor einer langsamen Anwendung geschaltet werden, ohne zusammenzubrechen – weshalb es zum Standard-Einstiegspunkt für das moderne Web wurde.

Nginx übernimmt drei Rollen, die oft vermischt werden. Es ist ein statischer Webserver, der Dateien effizient von der Festplatte ausliefert. Es ist ein reverse proxy, der Anfragen an einen anderen Server weiterleitet und die Antwort zurückgibt. Und es ist ein load balancer, der Anfragen auf einen Pool von Backends verteilt. Vor einer Node, Python, Go oder PHP App werden Sie alle drei Funktionen nutzen. Nginx ist kein Teil Ihrer Anwendung: es ist ein separater Prozess mit eigener Konfiguration, eigenem Lebenszyklus und eigenen Logs.

Warum man es vor die App schaltet

Man könnte Node direkt an Port 80 binden. In Tutorials wird das oft so gemacht, und es funktioniert – bis es nicht mehr funktioniert. Nginx als Reverse Proxy davor zu schalten, bietet eine Reihe von Funktionen, die mühsam oder riskant zu implementieren wären, wenn man sie direkt in die Anwendung einbaut.

TLS-Terminierung. Nginx verwaltet das Zertifikat, führt den Handshake durch und entschlüsselt die Daten. Deine App kommuniziert über einfaches HTTP auf localhost und muss sich nie um die Zertifikatserneuerung kümmern. Certbot kann die Konfiguration sogar automatisch für dich anpassen.

Statische Dateien, Komprimierung und Caching. Nginx liefert Dateien mit sendfile und Kernel-Caching aus, komprimiert diese mit gzip oder brotli und kann eine gespeicherte Antwort zurückgeben, ohne den Upstream überhaupt zu kontaktieren.

Rate Limiting und Request-Limits. Missbrauch wird direkt am Edge gestoppt, bevor er den Connection Pool oder eine Datenbankabfrage belastet.

Ein einziger Einstiegspunkt. Eine einzige öffentliche Adresse und ein Zertifikat können viele Dienste bedienen, die über den Hostnamen oder Pfad geroutet werden. Du kannst eine App auf einen anderen Port oder Host verschieben, ohne den DNS ändern zu müssen.

Zero-Downtime-Deployments. Ein Upstream kann den Traffic von einem Backend ableiten, während dieses neu startet, und ein Neuladen der Konfiguration erfolgt reibungslos (graceful). Direkt exponiertes Node bricht beim Neustart jede aktive Verbindung ab.

Das Konfigurationsmodell

Die Nginx-Konfiguration ist ein Baum aus Contexts und Directives. Eine Directive ist eine Einstellung, die mit einem Semikolon endet; ein Context ist ein in geschweiften Klammern eingeschlossener Block, der weitere Directives enthält. Directives werden nach unten hin vererbt, wobei der spezifischste Context Vorrang hat.

# /etc/nginx/nginx.conf (main context)
user  www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
  worker_connections 1024;
}

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  sendfile      on;
  keepalive_timeout 65;

  gzip on;
  gzip_types text/css application/javascript application/json;

  # site configs are pulled in here
  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

Die Contexts, mit denen Sie arbeiten werden, sind:

  • main — globale Einstellungen wie user und worker_processes.
  • events — wie Verbindungen gehandhabt werden.
  • http — alles rund um HTTP: MIME-Typen, Logging, Kompression und die Includes, die die Website-Dateien einbinden.
  • server — ein virtueller Host, der über listen und server_name ausgewählt wird.
  • location — ein Pfad innerhalb eines Servers, an dem Anfragen tatsächlich verarbeitet werden.

Unter Debian und Ubuntu ist es üblich, pro Website eine Datei in /etc/nginx/sites-available/ zu führen und die aktivierten Seiten per Symlink nach /etc/nginx/sites-enabled/ zu verknüpfen. Dies bietet einen einfachen Weg, eine Website zu deaktivieren, ohne sie löschen zu müssen.

sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
sudo rm /etc/nginx/sites-enabled/app

Directives sind keine Skriptsprache. Es gibt keine Schleifen oder Variablen im herkömmlichen Sinne – nur map, if (sparsam einzusetzen) und Includes. Wenn ein Problem nach einer Steuerungslogik verlangt, ist die Lösung meist ein map oder eine Änderung an der Anwendung.

Dein erster server block

Ein server block ist ein virtueller Host. Er hört auf einem Port, antwortet auf einen oder mehrere Namen und enthält location blocks.

server {
  listen 80;
  server_name app.example.com www.app.example.com;

  root /var/www/app/dist;
  index index.html;

  location / {
    try_files $uri $uri/ =404;
  }
}

server_name wird mit dem Host header abgeglichen. Der erste server block für einen Port ist der default server und fängt Anfragen ab, deren Host mit nichts anderem übereinstimmt. Daher lohnt es sich, diesen explizit als Catch-all einzurichten, der einen 444-Fehler oder eine Wartungsseite zurückgibt, anstatt versehentlich eine nicht beabsichtigte Seite auszuliefern.

Die Reihenfolge des Matchings ist entscheidend und führt oft zu Fehlern. location blocks werden zuerst als exakte Treffer (=), dann als längster Prefix-Match und schließlich über Regexes (~ und ~*) geprüft. Ein ^~ prefix weist Nginx an, die Suche zu stoppen und diesen Prefix zu verwenden, ohne Regexes zu prüfen. Wenn sich das Verhalten falsch anfühlt, liegt das meist an dieser Reihenfolge.

location = /favicon.ico { log_not_found off; access_log off; }
location ^~ /assets/    { expires 1y; }
location ~* \.(jpg|png|css|js)$ { expires 30d; }
location /              { try_files $uri /index.html; }

Proxying zu einer Node.js App

Die zentrale Direktive ist proxy_pass. Richten Sie diese auf Ihre Anwendung aus, und Nginx fungiert als Reverse Proxy.

upstream app {
  server 127.0.0.1:3000;
  keepalive 32;
}

server {
  listen 80;
  server_name app.example.com;

  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_read_timeout 30s;
    proxy_connect_timeout 5s;
  }
}

Die Definition eines upstream-Blocks anstelle der Inline-Angabe der Adresse dient nicht nur der Unterstützung mehrerer Server. Erst dadurch können Sie keepalive festlegen, sodass Nginx Verbindungen zum Backend wiederverwendet, anstatt für jede Anfrage eine neue TCP-Verbindung zu öffnen. proxy_http_version 1.1 ist für Keepalive und für WebSockets erforderlich.

Timeouts sollten sorgfältig geplant werden. proxy_connect_timeout begrenzt die Zeit, die Nginx auf den Verbindungsaufbau wartet; proxy_read_timeout begrenzt die Zeit, die auf das nächste Byte vom Upstream gewartet wird. Der Read-Timeout muss länger sein als Ihre langsamste legitime Antwort, andernfalls gibt Nginx einen 504-Fehler zurück, während die App noch arbeitet.

Die Regel mit dem abschließenden Slash ist eine klassische Stolperfalle. proxy_pass http://app; leitet den vollständigen Pfad unverändert weiter. proxy_pass http://app/; ersetzt das gefundene Location-Präfix durch /. Innerhalb von location /api/ sendet die erste Variante /api/users und die zweite /users. Wählen Sie bewusst und prüfen Sie das Upstream-Log.

Header-Weiterleitung und warum sie für deine App wichtig ist

Standardmäßig sieht der Upstream die Anfrage so, als käme sie von Nginx: Die Client-Adresse ist 127.0.0.1, das Schema ist http und der Host-Header fehlt möglicherweise. Das führt zu Problemen beim Logging, bei Redirects, Cookies und beim Rate Limiting innerhalb der App.

location / {
  proxy_pass http://app;
  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;
  proxy_set_header X-Forwarded-Host  $host;
  proxy_set_header X-Forwarded-Port  $server_port;
}

Jeder Header hat seine Daseinsberechtigung:

  • Host bewahrt die ursprüngliche Domain, sodass die App korrekte absolute URLs und Virtual Hosts erstellen kann.
  • X-Real-IP ist die unmittelbare Client-Adresse.
  • X-Forwarded-For ergänzt eine Kette; $proxy_add_x_forwarded_for behält alle vorhandenen Einträge bei und fügt den aktuellen Client hinzu. Nutze den ersten Eintrag als ursprünglichen Client, aber vertraue ihm niemals blind.
  • X-Forwarded-Proto teilt der App mit, ob sich der Benutzer über HTTPS verbunden hat, was Redirect-Loops und Bugs bei Secure-Cookies behebt.
  • X-Forwarded-Host und X-Forwarded-Port helfen weiter, wenn Nginx auf einem nicht standardmäßigen Port lauscht.

Deinem Framework muss mitgeteilt werden, dass es diesem Proxy vertrauen soll. In Express sorgt app.set("trust proxy", 1) dafür, dass req.ip und req.protocol die weitergeleiteten Werte auslesen. Wenn du das überspringst, scheint jede Anfrage von localhost zu kommen, was stillschweigend das per-IP Rate Limiting und die Geolocation beeinträchtigt.

Statische Dateien ausliefern und der SPA-Fallback

Nginx ist hervorragend darin, statische Dateien auszuliefern – überlassen Sie ihm also diese Aufgabe. Eine gängige Aufteilung besteht darin, das gebaute Frontend direkt von der Festplatte zu servieren und nur die API zu proxien.

server {
  listen 80;
  server_name app.example.com;
  root /var/www/app/dist;

  location /assets/ {
    try_files $uri =404;
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  location /api/ {
    proxy_pass http://app;
    proxy_set_header Host $host;
  }

  location / {
    try_files $uri /index.html;
  }
}

Für eine Single-Page App ist try_files $uri /index.html der Fallback, der das clientseitige Routing ermöglicht: Wenn der angeforderte Pfad keine Datei ist, wird index.html zurückgegeben und der Router übernimmt die Steuerung. Für eine API ist dasselbe Muster mit =404 besser, da das Zurückgeben von HTML bei einem fehlenden Endpunkt Bugs verschleiern würde.

Assets mit Content-Hashes im Namen können dauerhaft gecached werden — immutable und ein Ablaufdatum von einem Jahr sind sicher, da jede Änderung einen neuen Dateinamen erzeugt. index.html darf niemals auf diese Weise gecached werden, da die Nutzer sonst dauerhaft eine veraltete Version der App laden.

TLS mit Let’s Encrypt und certbot

Certbot bezieht kostenlose Zertifikate von Let’s Encrypt und kann Nginx automatisch konfigurieren. Installieren Sie das Plugin, führen Sie es einmal pro Domain aus, und das Tool erledigt den Rest.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com -d www.app.example.com
sudo certbot renew --dry-run

Certbot schreibt die Pfade zu den Zertifikaten in Ihren Server-Block und installiert einen Timer für die Erneuerung. Zertifikate sind 90 Tage gültig; die Erneuerung erfolgt automatisch, funktioniert jedoch nur, wenn die Erneuerungsprüfung Ihren Server über Port 80 erreichen kann. Sperren Sie diesen also nicht vollständig über die Firewall ab.

Der resultierende Block sieht so aus: ein HTTP-Server, der weiterleitet, und ein HTTPS-Server, der die Verbindung terminiert.

server {
  listen 80;
  server_name app.example.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
  ssl_protocols       TLSv1.2 TLSv1.3;
  ssl_session_cache   shared:SSL:10m;
  ssl_session_timeout 1d;

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;

  location / {
    proxy_pass http://app;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

TLSv1.2 und TLSv1.3 sind heutzutage die einzigen Protokolle, die es wert sind, aktiviert zu werden. HSTS weist Browser an, einfaches HTTP für ein Jahr abzulehnen. Aktivieren Sie dies daher erst, wenn Sie sicher sind, dass HTTPS überall funktioniert – es ist schwierig, dies wieder rückgängig zu machen.

HTTP/2 und HTTP/3

HTTP/2 multiplexed viele Anfragen über eine einzige Verbindung und wird pro Server aktiviert. In modernen Nginx-Versionen (1.25.1 und neuer) lautet die Direktive http2 on;. In älteren Versionen ist es ein Flag in der listen-Zeile: listen 443 ssl http2;.

server {
  listen 443 ssl;
  http2 on;
  # ...
}

HTTP/3 läuft über QUIC und UDP. Es benötigt einen Build mit dem QUIC-Modul, eine separate listen 443 quic reuseport;-Zeile und einen Alt-Svc: h3=":443"; ma=86400-Header, um es anzukündigen. Betrachten Sie dies als Optimierung, die Sie implementieren, sobald HTTP/2 stabil läuft, und nicht als ersten Schritt. Browser handeln beides automatisch über TLS aus, sodass in Ihrer App nichts geändert werden muss.

Load-Balancing-Methoden

Ein upstream-Block definiert einen Pool, und die Balancing-Methode entscheidet, welcher Server die nächste Anfrage erhält. Standardmäßig wird Round-robin verwendet.

upstream app {
  least_conn;
  server 10.0.0.11:3000 weight=2 max_fails=3 fail_timeout=15s;
  server 10.0.0.12:3000;
  server 10.0.0.13:3000 backup;
  keepalive 64;
}
  • Round-robin (Standard) durchläuft die Server zyklisch. Einfach und effektiv, wenn Anfragen in etwa die gleiche Last verursachen.
  • least_conn sendet Anfragen an den Server mit den wenigsten aktiven Verbindungen. Besser geeignet, wenn die Dauer der Anfragen stark variiert, was bei APIs häufig der Fall ist.
  • ip_hash bindet einen Client durch Hashing der Adresse an einen bestimmten Server. Nützlich für In-Memory-Sessions, allerdings auf Kosten einer ungleichmäßigen Lastverteilung und des Verlusts der Stickiness, wenn ein Server ausfällt.
  • weight lenkt den Traffic bevorzugt auf eine leistungsstärkere Maschine oder wird bei einem Canary-Release eingesetzt.
  • backup markiert einen Server, der nur dann Traffic erhält, wenn die primären Server nicht verfügbar sind.

max_fails und fail_timeout ermöglichen ein passives Health Checking: Nach max_fails Fehlern innerhalb von fail_timeout markiert Nginx den Server für diesen Zeitraum als nicht verfügbar. proxy_next_upstream entscheidet dann, welche Fehler auf einem anderen Backend erneut versucht werden.

proxy_next_upstream error timeout http_502 http_503 http_504;

Retries sind mächtig, aber nicht kostenlos: Sie ergeben nur bei idempotenten Anfragen Sinn. Versuchen Sie niemals blind einen POST-Request erneut, der möglicherweise bereits den Zustand geändert hat, es sei denn, das Upstream ist per Design idempotent.

Health Checks und Fehlerbehandlung

Nginx OSS verfügt über passive Checks; aktive Health Checks (health_check mit match) erfordern Nginx Plus. Passive Checks in Kombination mit einem Endpoint auf Anwendungsebene reichen für die meisten Teams völlig aus: Stellen Sie /healthz bereit, der die benötigten Abhängigkeiten – wie Datenbank und Cache – prüft und schnell antwortet, und richten Sie dann die Liveness Probe Ihrer Plattform darauf aus. Nutzen Sie dies, um Deployments zu steuern, indem Sie ein neues Backend starten, warten, bis es den Status „healthy“ erreicht hat, und anschließend das alte aus dem Upstream entfernen.

Da Konfigurations-Reloads „graceful“ ablaufen, besteht das Zero-Downtime-Muster darin, den Upstream so zu bearbeiten, dass er auf die neue Instanz zeigt, nginx -t, einen Reload durchzuführen und danach die alte Instanz auslaufen zu lassen (drain) und zu stoppen.

Caching mit proxy_cache

proxy_cache speichert Upstream-Antworten auf der Festplatte und liefert diese aus, ohne das Backend zu kontaktieren. Für leseintensive Endpunkte, die sich nur langsam ändern, ist dies der größte Performance-Gewinn, der auf der Edge-Ebene möglich ist.

proxy_cache_path /var/cache/nginx levels=1:2
                 keys_zone=app:10m max_size=1g inactive=60m;

server {
  listen 80;
  server_name app.example.com;

  location /api/public/ {
    proxy_pass http://app;
    proxy_cache app;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    proxy_cache_valid 200 302 10s;
    proxy_cache_valid 404 1m;
    proxy_cache_bypass $http_authorization $cookie_session;
    proxy_no_cache $http_authorization;
    add_header X-Cache-Status $upstream_cache_status;
  }
}

proxy_cache_valid legt fest, wie lange jeder Statuscode gecacht wird. proxy_cache_bypass überspringt den Cache für Anfragen, die einen Session- oder Authorization-Header enthalten; proxy_no_cache verhindert das Speichern dieser Antworten, sodass die privaten Daten eines Benutzers niemals an einen anderen ausgeliefert werden. Der X-Cache-Status Header (HIT, MISS, BYPASS, EXPIRED) ist der schnellste Weg, um zu beweisen, dass das Caching funktioniert.

Zwei Warnhinweise: Cachen Sie nur Antworten, die sicher geteilt werden können – also öffentliche, nicht personalisierte Daten – und stellen Sie sicher, dass der Upstream einen Cache-Control Header sendet, der mit Ihrer Konfiguration übereinstimmt. Ein Cache-Key, der eine Variante (z. B. einen Language-Header oder einen Query-Parameter) ignoriert, wird bereitwillig den falschen Inhalt ausliefern.

Rate Limiting mit limit_req

Rate Limiting schützt Login-Endpoints, die Suche und öffentliche APIs vor Missbrauch und versehentlichen Floods. Es wird im http-Kontext definiert und in einem Location-Block angewendet.

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_status 429;

server {
  listen 80;
  server_name app.example.com;

  location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://app;
  }

  location = /api/login {
    limit_req zone=login burst=5 nodelay;
    proxy_pass http://app;
  }
}

Der Zone-Key ist normalerweise $binary_remote_addr (die Client-IP), und die Rate wird in Anfragen pro Sekunde oder Minute angegeben. burst erlaubt kurze Spitzen (Spikes), und nodelay verarbeitet den Burst sofort, anstatt ihn zu verteilen – was für interaktive Endpoints wünschenswert ist. Ohne nodelay werden überschüssige Anfragen verzögert, was dazu führen kann, dass sich eine UI träge anfühlt, anstatt einfach einen 429-Fehler zurückzugeben.

Hinter einem anderen Proxy oder einem CDN kann $binary_remote_addr die Adresse des Proxys sein, sodass der Limiter alle Nutzer als einen einzigen Client behandelt. Beheben Sie dies, indem Sie set_real_ip_from und real_ip_header konfigurieren, damit Nginx den tatsächlichen Client erkennt. Geben Sie zudem 429 anstelle des Standardwerts 503 zurück, damit Clients zwischen Rate Limiting und einem Systemausfall unterscheiden können.

WebSocket-Upgrade

Eine WebSocket-Verbindung beginnt als HTTP-Request mit einem Upgrade-Header und wird anschließend zu einem langlebigen, bidirektionalen Stream. Für das Proxying sind die Upgrade-Header sowie ein Connection-Wert erforderlich, der vom Request abhängt, weshalb ein map verwendet wird.

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  location /ws/ {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host       $host;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
  }
}

Das read_timeout ist hierbei entscheidend: Ein inaktiver WebSocket mit einem kurzen Timeout wird von Nginx geschlossen, was beim Client als mysteriöser Verbindungsabbruch erscheint. Beachten Sie, dass HTTP/2 WebSockets nicht auf die gleiche Weise transportiert; Browser öffnen für das Upgrade eine separate HTTP/1.1-Verbindung, die Nginx automatisch auf demselben Port handhabt.

Nginx in Docker ausführen

Das offizielle nginx Image ist ein praktischer Weg, um Konfigurationen und statische Dateien zusammen mit deiner App auszuliefern. Die einzige Regel ist, dass der Hauptprozess des Containers im Vordergrund bleiben muss, weshalb der Befehl mit daemon off; endet.

# Dockerfile
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/app.conf
COPY dist/ /usr/share/nginx/html/
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

In Compose ist der App-Service über seinen Servicenamen erreichbar, daher ist der Upstream-Host einfach app. Nginx startet, bevor die App bereit ist; füge daher entweder einen Healthcheck hinzu und richte eine Abhängigkeit darauf ein, oder lass Nginx die Anfrage wiederholen.

# docker-compose.yml
services:
  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/app.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      app:
        condition: service_healthy
  app:
    build: .
    expose:
      - "3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
      interval: 5s
      retries: 10

Beachte expose anstelle von ports bei der App: Diese sollte nur innerhalb des Compose-Netzwerks erreichbar sein, niemals direkt vom Host aus. Der Upstream in der Nginx-Konfiguration wird zu server app:3000;.

Testen, Neuladen und Zero-Downtime-Konfigurationsänderungen

Jede Konfigurationsänderung sollte demselben Zyklus folgen: validieren, neuladen, verifizieren. Ein Reload ist graceful; ein Neustart hingegen nicht.

sudo nginx -t                 # test the configuration
sudo nginx -T                 # test and dump the full effective config
sudo systemctl reload nginx   # graceful, no dropped connections
sudo systemctl status nginx
curl -I https://app.example.com

nginx -T wird zu selten genutzt und ist extrem hilfreich: Es gibt die zusammengeführte Konfiguration genau so aus, wie Nginx sie sieht, was Diskussionen über Vererbung und Includes beendet. Wenn -t fehlschlägt, nennt die Fehlermeldung die entsprechende Datei und Zeile.

Beim Neuladen sollte in einer zweiten Session eine Shell geöffnet bleiben, damit eine fehlerhafte Konfiguration Sie nicht aussperrt. In einem Container entspricht dies dem Senden von SIGHUP an den Master-Prozess oder dem Ausführen von nginx -s reload. Starten Sie einen stark ausgelasteten Produktions-Nginx niemals neu, um eine Konfigurationsänderung anzuwenden, wenn ein Reload ausreicht.

Best Practices

  • Führen Sie nginx -t vor jedem Reload aus; editieren und starten Sie niemals blind neu.
  • Verwenden Sie eine Datei pro Seite und aktivieren Sie diese über einen Symlink, damit Änderungen rückgängig gemacht werden können.
  • Definieren Sie einen upstream-Block mit keepalive, anstatt die Backend-Adresse inline einzufügen.
  • Leiten Sie Host, X-Real-IP, X-Forwarded-For und X-Forwarded-Proto immer weiter und setzen Sie trust proxy in der App.
  • Servieren Sie statische Assets und gehashte Bundles von der Festplatte mit langen, immutable Cache-Headern.
  • Leiten Sie HTTP auf HTTPS um und aktivieren Sie HSTS erst, wenn HTTPS nachweislich funktioniert.
  • Cachen Sie nur öffentliche, nicht-personalisierte Antworten und verwenden Sie niemals einen Cache-Key ohne dessen Varianten.
  • Implementieren Sie Rate Limiting für Login- und öffentliche Endpunkte und geben Sie 429 statt 503 zurück.
  • Lange Timeouts für WebSocket-Locations, kurze für Health Checks.
  • Halten Sie Secrets und Zertifikate aus den Images heraus; mounten Sie diese zur Laufzeit schreibgeschützt.

Häufige Fehler

  • nginx -t überspringen und die Seite durch einen Syntaxfehler offline nehmen.
  • X-Forwarded-Proto vergessen und anschließend eine Endlosschleife aus HTTPS-Redirects debuggen müssen.
  • trust proxy in der App nicht aktivieren, sodass jede Anfrage so aussieht, als käme sie von localhost.
  • proxy_pass mit und ohne abschließenden Slash verwechseln und dadurch 404-Fehler erhalten.
  • proxy_read_timeout kürzer einstellen als eine legitime langsame Antwort, was zu 504-Fehlern führt.
  • Personalisierte Antworten cachen und so die Daten eines Benutzers an einen anderen ausliefern.
  • WebSockets ohne den Upgrade-map proxien, wodurch Verbindungen sofort abbrechen.
  • Den Standard-Server als ersten Site-Block stehen lassen und so eine unbeabsichtigte App exponieren.
  • HSTS aktivieren, bevor HTTPS überall funktioniert, und Benutzer für ein Jahr aussperren.
  • Den App-Service in Compose mit ports statt mit expose exponieren.

Wie geht es weiter?

Nginx ist das Eingangstor, und der Guide zu HTTP / HTTPS erklärt das Protokoll, das Nginx terminiert und weiterleitet. Um Nginx reproduzierbar neben deiner App zu betreiben, lies Docker & Deployment, und um den Host, auf dem es läuft, gesund zu halten, schau dir noch einmal Linux an. Wenn du bereit bist, Nginx mit Zertifikaten, DNS und Autoscaling im öffentlichen Internet bereitzustellen, führt der Guide zu Cloud Deployment diese Einzelteile zusammen.

In der Praxis

Die vier Konfigurationen, die Sie am häufigsten schreiben werden

Beginnen Sie mit einem Proxy, fügen Sie TLS hinzu, skalieren Sie auf einen Pool und ergänzen Sie dann Caching und Rate Limiting.

sites-available/app
upstream app {
  server 127.0.0.1:3000;
  keepalive 32;
}

server {
  listen 80;
  server_name app.example.com;

  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    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;
    proxy_read_timeout 30s;
  }
}

Reverse Proxy vs. direkte App-Exposition

Node auf Port 80 zu betreiben bedeutet Verzicht auf TLS, statisches Caching, Rate Limiting und Zero-Downtime-Deployments. Nginx kostet einen Prozess und liefert all das.

Bevorzugt
server {
  listen 443 ssl;
  server_name app.example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
  }
}
Vermeiden
# Node listening on :80 directly
# - no TLS termination
# - no static file cache
# - no rate limiting
# - restart drops connections
# - one process owns the port

ip_hash vs. round-robin

Round-robin verteilt die Last gleichmäßig. ip_hash bindet einen Client an ein Backend, was nützlich für Sticky Sessions ist, aber zu ungleichmäßiger Last führt und die Bindung verliert, wenn ein Server ausfällt.

Gleichmäßige Last
upstream app {
  least_conn;
  server 10.0.0.11:3000;
  server 10.0.0.12:3000;
}
# Stateless apps and shared
# session stores scale cleanly.
Sticky Sessions
upstream app {
  ip_hash;
  server 10.0.0.11:3000;
  server 10.0.0.12:3000;
}
# Pins clients, but a restart
# reshuffles their sessions.

Abwägungen

Ist Nginx den zusätzlichen Prozess wert?

Nginx bringt eine eigene Konfigurationssprache und eine weitere Komponente beim Deployment mit sich. Für jeden öffentlichen Dienst zahlen sich die Edge-Features jedoch schnell aus.

Strengths

  • Sicherheit an der Edge

    TLS, Security-Header, Request-Größenlimits und Rate Limiting werden erzwungen, bevor eine Anfrage den Anwendungscode erreicht, wodurch Bugs weniger exponiert sind.

  • Performance geschenkt

    Statische Dateien, gzip und gecachte Antworten werden von einem event-gesteuerten C-Server ausgeliefert, was den Single-Thread von Node für die eigentliche Arbeit freimacht.

  • Deployments ohne Downtime

    Das Neuladen von Nginx erfolgt graceful, und ein Upstream kann ein Backend entlasten, während es neu startet, sodass Nutzer nie eine 'Connection Refused' sehen.

Trade-offs

  • Ein weiteres Tool zum Lernen und Betreiben

    Konfigurationssyntax, Reihenfolge-Regeln und Reload-Semantik sind eine echte Lernkurve. Ein Tippfehler kann eine Seite offline nehmen, wenn man nginx -t überspringt.

  • TLS-Terminierung verschiebt die Grenze

    Der Traffic zwischen Nginx und dem Upstream ist einfaches HTTP. Auf einem Shared Host oder in einem Netzwerk sollte auch dieser Hop verschlüsselt oder isoliert werden.

  • Kann Anwendungsprobleme maskieren

    Caching und Buffering kaschieren langsame Origins, und Forwarding-Header werden leicht vergessen, was zu falschen Client-IPs und defekten Redirects führt.

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, Nginx / Reverse Proxy zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch Nginx / Reverse Proxy — mit Quizzen und echtem Code, den Sie im Browser ausführen können.