💡 Hinweis: Dieses Tutorial setzt voraus, dass du einen normalen Benutzer mit sudo-Rechten verwendest — wie im Tutorial Debian 13 Server absichern beschrieben.
1. Einleitung — Was ist ein Reverse Proxy?
Stell dir vor, du betreibst auf einem einzigen Server mehrere Webanwendungen: eine Node.js-App auf Port 3000, eine API auf Port 8080 und vielleicht noch ein Dashboard auf Port 9090. Alle sollen über Port 80/443 erreichbar sein — mit eigener Subdomain und SSL-Zertifikat. Genau das macht ein Reverse Proxy.
Ein Reverse Proxy sitzt zwischen dem Internet und deinen Backend-Services. Er nimmt eingehende Anfragen entgegen und leitet sie — basierend auf Domain, Pfad oder anderen Kriterien — an den richtigen internen Service weiter. Der Client merkt davon nichts: Für ihn sieht es so aus, als käme alles von einem einzigen Server.
Warum brauchst du einen Reverse Proxy?
- Ein einziger Einstiegspunkt: Alle Services laufen hinter Port 443 mit SSL — keine kryptischen Port-Nummern in der URL
- SSL-Terminierung: Nginx kümmert sich um HTTPS, deine Apps müssen sich nicht darum kümmern
- Sicherheit: Deine Backend-Ports sind nicht direkt aus dem Internet erreichbar
- Load Balancing: Verteile Traffic auf mehrere Instanzen einer App
- Caching: Häufig angefragte Inhalte direkt aus dem Proxy liefern
- Zentrale Konfiguration: Rate Limiting, Logging und Security-Header an einer Stelle
Nginx ist dafür die populärste Wahl — leichtgewichtig, extrem performant und millionenfach bewährt.
2. Voraussetzungen
Dieses Tutorial baut auf den vorherigen Teilen der Serie auf:
- Debian 13 (Trixie) als Betriebssystem — frisch installiert oder bereits eingerichtet
- Nginx installiert und lauffähig — siehe Tutorial #2: Nginx auf Debian 13 installieren
- SSL mit Let’s Encrypt eingerichtet — siehe Tutorial #3: SSL-Zertifikate mit Certbot
- Root-Zugang oder ein User mit
sudo-Rechten - Eine Domain mit DNS-Zugriff (für Subdomains)
Falls du die vorherigen Tutorials noch nicht durchgearbeitet hast, hole das jetzt nach. Ab hier gehe ich davon aus, dass Nginx läuft und du weißt, wie Server-Blöcke funktionieren.
# Schneller Check: Läuft Nginx?
sudo systemctl status nginx
# Ausgabe sollte "active (running)" zeigen3. Konzept: Ein Server, mehrere Services auf verschiedenen Ports
Bevor wir konfigurieren, solltest du das Grundkonzept verstehen. Hier ein typisches Setup:
Internet
│
▼
┌──────────────────────┐
│ Nginx (Port 80/443) │ ← Reverse Proxy
└──────┬───┬───┬───────┘
│ │ │
▼ ▼ ▼
:3000 :8080 :9090
App API DashboardDer Ablauf bei jeder Anfrage:
- Ein Client ruft
https://app.example.comauf - Nginx empfängt die Anfrage auf Port 443
- Nginx prüft den
server_nameund findet den passenden Server-Block - Die Direktive
proxy_passleitet die Anfrage intern anhttp://127.0.0.1:3000weiter - Die App antwortet, Nginx reicht die Antwort an den Client durch
Wichtig: Die Backend-Services lauschen nur auf 127.0.0.1 (localhost) — sie sind von außen nicht direkt erreichbar. Nur Nginx ist dem Internet ausgesetzt.
4. Grundlegende Reverse-Proxy-Konfiguration
Die zwei wichtigsten Direktiven für einen Reverse Proxy sind proxy_pass und proxy_set_header.
proxy_pass — Wohin wird weitergeleitet?
location / {
proxy_pass http://127.0.0.1:3000;
}Das ist die Minimalversion. Nginx leitet alle Anfragen an den lokalen Service auf Port 3000 weiter. Aber so simpel solltest du es nicht konfigurieren — denn wichtige Informationen gehen verloren.
proxy_set_header — Kontext weitergeben
Wenn Nginx eine Anfrage weiterleitet, gehen standardmäßig einige Informationen verloren. Dein Backend sieht dann nur 127.0.0.1 als Client-IP und weiß nicht, ob die Original-Anfrage per HTTPS kam. Mit proxy_set_header reichst du diese Informationen durch:
location / {
proxy_pass http://127.0.0.1:3000;
# Original-Host-Header durchreichen
proxy_set_header Host $host;
# Echte Client-IP weitergeben
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Dem Backend mitteilen, ob HTTPS verwendet wurde
proxy_set_header X-Forwarded-Proto $scheme;
}Was diese Header bedeuten:
| Header | Zweck |
|---|---|
Host | Der Original-Hostname aus der Anfrage (z.B. app.example.com) |
X-Real-IP | Die echte IP-Adresse des Clients |
X-Forwarded-For | Kette aller Proxies, durch die die Anfrage ging |
X-Forwarded-Proto | http oder https — das Original-Protokoll |
Vollständiger Basis-Template
Hier ist ein Template, das du als Ausgangspunkt für jeden Reverse-Proxy-Block verwenden kannst:
server {
listen 80;
server_name app.example.com;
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;
# Timeouts anpassen (optional, aber empfohlen)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}5. Praxis-Beispiel 1: Node.js App hinter Nginx
Lass uns eine echte Node.js-Anwendung hinter Nginx stellen. Zuerst erstellen wir eine minimale App zum Testen.
Node.js installieren (falls noch nicht geschehen)
# Node.js 22 LTS via NodeSource
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt install -y nodejs
# Version prüfen
node --version
npm --versionTest-App erstellen
# Verzeichnis anlegen
sudo mkdir -p /var/www/myapp
cd /var/www/myapp
# Minimale Express-App
sudo tee app.js > /dev/null <<'EOF'
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from Node.js!',
headers: req.headers,
ip: req.headers['x-real-ip'] || req.ip
});
});
app.listen(PORT, '127.0.0.1', () => {
console.log(`App running on http://127.0.0.1:${PORT}`);
});
EOF
# Dependencies installieren
sudo npm init -y
sudo npm install expressBeachte: Die App bindet sich an 127.0.0.1, nicht an 0.0.0.0. Das ist wichtig — sie soll nur lokal erreichbar sein.
Systemd-Service für die App
Damit die App beim Serverstart automatisch läuft und bei Abstürzen neu startet:
sudo tee /etc/systemd/system/myapp.service > /dev/null <<'EOF'
[Unit]
Description=My Node.js App
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node app.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myappNginx-Konfiguration
sudo tee /etc/nginx/sites-available/myapp > /dev/null <<'EOF'
server {
listen 80;
server_name app.example.com;
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;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
EOF
# Aktivieren und testen
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxTesten
# Lokal testen (direkt)
curl http://127.0.0.1:3000
# Über Nginx testen
curl -H "Host: app.example.com" http://127.0.0.1In der JSON-Antwort solltest du die durchgereichten Header sehen — x-real-ip, x-forwarded-for und x-forwarded-proto.
6. Praxis-Beispiel 2: Docker-Container hinter Nginx
Docker-Container sind ein häufiger Use Case für Reverse Proxies. Jeder Container bekommt seinen eigenen Port, Nginx leitet den Traffic weiter.
Docker installieren (falls nötig)
# Docker-Repository hinzufügen
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian trixie stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.ioBeispiel: Grafana im Container
# Grafana starten (Dashboard auf Port 3001)
docker run -d \
--name grafana \
--restart unless-stopped \
-p 127.0.0.1:3001:3000 \
grafana/grafanaWichtig: Das 127.0.0.1: vor dem Port stellt sicher, dass der Container nur lokal erreichbar ist — nicht direkt aus dem Internet.
Nginx-Konfiguration für den Container
sudo tee /etc/nginx/sites-available/grafana > /dev/null <<'EOF'
server {
listen 80;
server_name grafana.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
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;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/grafana /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxDocker Compose mit mehreren Services
In der Praxis nutzt du oft Docker Compose mit mehreren Services:
# docker-compose.yml
services:
grafana:
image: grafana/grafana
ports:
- "127.0.0.1:3001:3000"
restart: unless-stopped
uptime-kuma:
image: louislam/uptime-kuma
ports:
- "127.0.0.1:3002:3001"
volumes:
- uptime-kuma-data:/app/data
restart: unless-stopped
gitea:
image: gitea/gitea
ports:
- "127.0.0.1:3003:3000"
volumes:
- gitea-data:/data
restart: unless-stopped
volumes:
uptime-kuma-data:
gitea-data:Jeder Service bekommt seinen eigenen Port auf 127.0.0.1 und eine eigene Nginx-Konfiguration mit passender Subdomain.
7. Mehrere Subdomains für verschiedene Services
DNS einrichten
Zuerst brauchst du DNS-Einträge für jede Subdomain. Bei deinem DNS-Provider (Cloudflare, Hetzner DNS, etc.):
# A-Records anlegen (alle auf die gleiche Server-IP)
app.example.com A 203.0.113.50
api.example.com A 203.0.113.50
grafana.example.com A 203.0.113.50
git.example.com A 203.0.113.50Alternativ kannst du einen Wildcard-DNS-Eintrag setzen:
*.example.com A 203.0.113.50Damit zeigen alle Subdomains auf deinen Server. Nginx entscheidet dann, welcher Service antwortet.
Nginx-Konfiguration für mehrere Subdomains
Jede Subdomain bekommt ihre eigene Konfigurationsdatei:
# /etc/nginx/sites-available/app.example.com
server {
listen 80;
server_name app.example.com;
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;
}
}
# /etc/nginx/sites-available/api.example.com
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
# /etc/nginx/sites-available/grafana.example.com
server {
listen 80;
server_name grafana.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
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;
}
}# Alle aktivieren
for site in app api grafana; do
sudo ln -sf /etc/nginx/sites-available/${site}.example.com /etc/nginx/sites-enabled/
done
sudo nginx -t && sudo systemctl reload nginxCatch-All für unbekannte Subdomains
Was passiert, wenn jemand eine Subdomain aufruft, die nicht konfiguriert ist? Ohne Catch-All antwortet Nginx mit dem ersten Server-Block. Besser ist ein expliziter Default:
server {
listen 80 default_server;
server_name _;
return 444; # Verbindung ohne Antwort schließen
}8. SSL für alle Subdomains
Jetzt sichern wir alle Subdomains mit SSL ab. Du hast zwei Optionen: einzelne Zertifikate oder ein Wildcard-Zertifikat.
Option A: Einzelne Zertifikate (einfach)
# Certbot für mehrere Domains auf einmal
sudo certbot --nginx \
-d app.example.com \
-d api.example.com \
-d grafana.example.com \
-d git.example.comCertbot modifiziert automatisch deine Nginx-Konfigurationen und fügt die SSL-Direktiven ein. Bei Renewal werden alle Zertifikate automatisch erneuert.
Option B: Wildcard-Zertifikat (eleganter)
Ein Wildcard-Zertifikat gilt für *.example.com — du musst nicht für jede neue Subdomain ein neues Zertifikat anfordern. Voraussetzung: DNS-Challenge (HTTP-Challenge funktioniert nicht für Wildcards).
# Cloudflare DNS Plugin installieren
sudo apt install -y python3-certbot-dns-cloudflare
# Cloudflare API-Token konfigurieren
sudo mkdir -p /etc/letsencrypt
sudo tee /etc/letsencrypt/cloudflare.ini > /dev/null <<EOF
dns_cloudflare_api_token = DEIN_CLOUDFLARE_API_TOKEN
EOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
# Wildcard-Zertifikat anfordern
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d example.com \
-d "*.example.com" \
--preferred-challenges dns-01Nginx SSL-Konfiguration mit Wildcard
Mit dem Wildcard-Zertifikat sieht ein Server-Block so aus:
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Moderne SSL-Einstellungen
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (optional, aber empfohlen)
add_header Strict-Transport-Security "max-age=63072000" always;
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;
}
}
# HTTP → HTTPS Redirect
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}SSL-Snippet für DRY-Konfiguration
Um nicht in jedem Server-Block die gleichen SSL-Einstellungen zu wiederholen, erstelle ein Snippet:
# /etc/nginx/snippets/ssl-wildcard.conf
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;Jetzt reicht in jedem Server-Block:
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
include snippets/ssl-wildcard.conf;
location / {
proxy_pass http://127.0.0.1:3000;
# ... Header wie gehabt
}
}9. WebSocket-Support
Viele moderne Anwendungen nutzen WebSockets — Chat-Apps, Live-Dashboards, Echtzeit-Notifications. Standardmäßig blockiert Nginx WebSocket-Verbindungen, weil sie ein Upgrade des HTTP-Protokolls erfordern.
Das Problem
Ein WebSocket-Handshake sieht so aus:
GET /ws HTTP/1.1
Host: app.example.com
Upgrade: websocket
Connection: UpgradeNginx leitet standardmäßig weder den Upgrade– noch den Connection-Header weiter. Das Ergebnis: Der WebSocket-Handshake schlägt fehl.
Die Lösung
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;
# WebSocket-Support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeout für WebSocket-Verbindungen erhöhen
# (Standard 60s würde idle Connections trennen)
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}Erklärung:
proxy_http_version 1.1— WebSockets erfordern HTTP/1.1 (nicht 1.0)proxy_set_header Upgrade $http_upgrade— Leitet den Upgrade-Header durchproxy_set_header Connection "upgrade"— Signalisiert das Connection-Upgradeproxy_read_timeout 86400s— 24 Stunden Timeout für langlebige WebSocket-Verbindungen
Nur bestimmte Pfade als WebSocket
Wenn nur ein bestimmter Pfad WebSockets nutzt (z.B. /ws oder /socket.io):
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
include snippets/ssl-wildcard.conf;
# Normaler HTTP-Traffic
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;
}
# WebSocket-Endpunkt
location /socket.io/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 86400s;
}
}10. Load Balancing Grundlagen
Was, wenn eine einzelne App-Instanz den Traffic nicht mehr schafft? Mit der upstream-Direktive kannst du den Traffic auf mehrere Backend-Instanzen verteilen.
Einfaches Round-Robin
upstream myapp_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
include snippets/ssl-wildcard.conf;
location / {
proxy_pass http://myapp_backend;
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;
}
}Standardmäßig verwendet Nginx Round-Robin — jede Anfrage geht an den nächsten Server in der Liste.
Weitere Strategien
# Gewichtung: Server 1 bekommt doppelt so viel Traffic
upstream myapp_backend {
server 127.0.0.1:3000 weight=2;
server 127.0.0.1:3001 weight=1;
}
# Least Connections: Anfrage geht an den Server mit den wenigsten Verbindungen
upstream myapp_backend {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
# IP Hash: Gleicher Client → Gleicher Server (Session-Persistence)
upstream myapp_backend {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}Health Checks und Failover
upstream myapp_backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 backup; # Nur wenn alle anderen down sind
}max_fails=3— Nach 3 fehlgeschlagenen Versuchen wird der Server als „down“ markiertfail_timeout=30s— Server bleibt 30 Sekunden als „down“ markiert, dann wird er erneut getestetbackup— Dieser Server wird nur aktiviert, wenn alle primären Server ausfallen
11. Caching & Buffering am Reverse Proxy
Nginx kann Antworten deiner Backend-Services zwischenspeichern. Das entlastet die Backends und beschleunigt die Auslieferung für wiederkehrende Anfragen.
Proxy-Cache einrichten
# In /etc/nginx/nginx.conf (im http-Block)
http {
# Cache-Zone definieren
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=proxy_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
# ...
}Parameter erklärt:
levels=1:2— Verzeichnisstruktur für Cache-Dateienkeys_zone=proxy_cache:10m— 10 MB Shared Memory für Cache-Keysmax_size=1g— Maximale Cache-Größe auf der Festplatteinactive=60m— Cache-Einträge werden nach 60 Minuten ohne Zugriff gelöscht
Cache im Server-Block aktivieren
server {
listen 443 ssl;
http2 on;
server_name api.example.com;
include snippets/ssl-wildcard.conf;
location / {
proxy_pass http://127.0.0.1:8080;
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;
# Caching aktivieren
proxy_cache proxy_cache;
proxy_cache_valid 200 10m; # 200er-Antworten 10 Minuten cachen
proxy_cache_valid 404 1m; # 404 nur 1 Minute
proxy_cache_use_stale error timeout updating;
# Cache-Status im Header (zum Debugging)
add_header X-Cache-Status $upstream_cache_status;
}
# Bestimmte Pfade NICHT cachen
location /api/auth {
proxy_pass http://127.0.0.1:8080;
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_no_cache 1;
proxy_cache_bypass 1;
}
}Buffering
Nginx puffert standardmäßig die Antworten des Backends, bevor sie an den Client gesendet werden. Das ist meistens gut — aber für Streaming-Anwendungen (Server-Sent Events, Chunked Responses) willst du es deaktivieren:
# Für SSE/Streaming-Endpunkte
location /events {
proxy_pass http://127.0.0.1:3000;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}12. Sicherheit: Rate Limiting & IP-Whitelisting
Rate Limiting
Schütze deine APIs vor Missbrauch und DDoS mit Rate Limiting:
# In /etc/nginx/nginx.conf (im http-Block)
http {
# Rate-Limit-Zone definieren: 10 Requests/Sekunde pro IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# Für Login-Endpunkte: Strenger (5 Requests/Minute)
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
}# Im Server-Block anwenden
server {
listen 443 ssl;
http2 on;
server_name api.example.com;
include snippets/ssl-wildcard.conf;
# Allgemeines Rate Limit für die API
location / {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
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;
}
# Strengeres Limit für Login
location /api/auth/login {
limit_req zone=login_limit burst=3 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
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;
}
}Parameter:
rate=10r/s— Maximal 10 Requests pro Sekundeburst=20— Bis zu 20 Anfragen dürfen kurzzeitig „vorauseilen“nodelay— Burst-Anfragen sofort bedienen, nicht verzögernlimit_req_status 429— HTTP 429 (Too Many Requests) statt dem Standard 503
IP-Whitelisting
Für Admin-Panels oder interne Services — nur bestimmte IPs zulassen:
# Nur bestimmte IPs erlauben
location /admin {
allow 203.0.113.10; # Dein Büro
allow 198.51.100.0/24; # VPN-Netz
deny all; # Alles andere blockieren
proxy_pass http://127.0.0.1:9090;
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;
}Geo-basiertes Blocking (optional)
# Bestimmte IPs oder Netze komplett blocken
geo $blocked {
default 0;
# Bekannte böswillige Netze
192.0.2.0/24 1;
198.51.100.0/24 1;
}
server {
if ($blocked) {
return 403;
}
# ...
}Security-Header für alle Proxied Services
# /etc/nginx/snippets/security-headers.conf
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;Einbinden mit include snippets/security-headers.conf; in jedem Server-Block.
13. Zusammenfassung & Checkliste
Hier ist deine Checkliste für jeden neuen Service hinter dem Reverse Proxy:
☐ Service-Checkliste
- Backend-Service läuft und bindet an
127.0.0.1:PORT - Systemd-Service oder Docker-Container mit
restart-Policy erstellt - DNS-Eintrag (A-Record) zeigt auf deinen Server
- Nginx-Konfiguration in
/etc/nginx/sites-available/erstellt - Symlink in
/etc/nginx/sites-enabled/gesetzt sudo nginx -terfolgreich ausgeführt- Nginx reload durchgeführt
- SSL-Zertifikat mit Certbot eingerichtet
- HTTP → HTTPS Redirect konfiguriert
- Firewall prüfen: Nur Port 80 und 443 offen
- Backend-Port nicht in der Firewall geöffnet
Empfohlene Firewall-Regeln
# UFW: Nur HTTP(S) und SSH erlauben
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw statusKurzfassung der wichtigsten Direktiven
| Direktive | Zweck |
|---|---|
proxy_pass | Ziel-URL des Backend-Service |
proxy_set_header | HTTP-Header an Backend durchreichen |
proxy_http_version 1.1 | Nötig für WebSockets und Keepalive |
upstream | Gruppe von Backend-Servern (Load Balancing) |
proxy_cache | Response-Caching aktivieren |
limit_req | Rate Limiting pro IP |
proxy_buffering off | Für Streaming/SSE deaktivieren |
14. Troubleshooting
502 Bad Gateway
Der häufigste Fehler. Bedeutet: Nginx konnte keine Verbindung zum Backend herstellen.
# 1. Läuft der Backend-Service überhaupt?
sudo systemctl status myapp
# oder
docker ps
# 2. Lauscht er auf dem richtigen Port?
sudo ss -tlnp | grep 3000
# 3. Ist er lokal erreichbar?
curl -v http://127.0.0.1:3000
# 4. Nginx Error-Log prüfen
sudo tail -f /var/log/nginx/error.logHäufige Ursachen:
- Service nicht gestartet
- Service bindet an anderen Port oder andere IP
- SELinux blockiert die Verbindung (bei aktiviertem SELinux:
setsebool -P httpd_can_network_connect 1) - Falsche proxy_pass URL (Trailing Slash beachten!)
504 Gateway Timeout
# Backend antwortet zu langsam — Timeouts erhöhen
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;Trailing Slash — Die Falle
Aufgepasst beim Trailing Slash in proxy_pass:
# Anfrage: /app/hello
# OHNE Trailing Slash — Pfad wird durchgereicht
location /app/ {
proxy_pass http://127.0.0.1:3000;
# Backend erhält: /app/hello
}
# MIT Trailing Slash — Pfad wird umgeschrieben
location /app/ {
proxy_pass http://127.0.0.1:3000/;
# Backend erhält: /hello (ohne /app)
}Header-Probleme debuggen
# Alle Header anzeigen, die beim Backend ankommen
curl -v https://app.example.com 2>&1 | grep -i "^[<>]"
# Oder im Backend loggen — Node.js Beispiel:
app.use((req, res, next) => {
console.log('Headers:', JSON.stringify(req.headers, null, 2));
next();
});WebSocket-Verbindung bricht ab
- Prüfe, ob
proxy_http_version 1.1gesetzt ist - Prüfe die
UpgradeundConnectionHeader - Erhöhe
proxy_read_timeoutfür langlebige Verbindungen - Wenn Cloudflare davor liegt: WebSockets müssen im Cloudflare-Dashboard aktiviert sein
Nützliche Debug-Befehle
# Nginx-Konfiguration validieren
sudo nginx -t
# Nginx-Konfiguration im Detail anzeigen
sudo nginx -T
# Aktive Verbindungen anzeigen
sudo nginx -T 2>/dev/null | grep -c "server_name"
# Echtzeit Error-Log
sudo tail -f /var/log/nginx/error.log
# Echtzeit Access-Log
sudo tail -f /var/log/nginx/access.log
# Prüfen, welche Ports belegt sind
sudo ss -tlnp
# DNS-Auflösung testen
dig app.example.com +short15. Nächste Schritte
Du hast jetzt ein solides Verständnis von Nginx als Reverse Proxy. Hier sind einige Richtungen, in die du weitergehen kannst:
- Monitoring: Nginx-Metriken mit Prometheus und Grafana überwachen —
stub_status-Modul aktivieren - Automatisierung: Ansible oder Terraform für Infrastructure-as-Code
- Container-Orchestrierung: Traefik als Alternative zu Nginx im Docker/Kubernetes-Umfeld
- Zero-Downtime Deployments: Rolling Updates mit
upstreamund Health Checks - ModSecurity: Web Application Firewall (WAF) für Nginx
- HTTP/3 (QUIC): Nginx unterstützt experimentell HTTP/3 — die Zukunft des Web-Protokolls
Im nächsten Tutorial der Serie schauen wir uns an, wie du Nginx-Performance tuning betreibst — Worker-Prozesse, Connection-Limits und Kernel-Parameter für maximale Performance unter Last.
Fragen oder Probleme? Schreib einen Kommentar — ich helfe gerne!
Schreibe einen Kommentar