nginx-Konfiguration — Server-Blöcke, Reverse Proxy und TLS

Praxis-Referenz zur nginx.conf — server- und location-Blöcke, Reverse Proxy, TLS, Gzip, Caching, Rate Limiting und Security-Header mit Beispielen.

Die nginx.conf ist das Herzstück jedes nginx-Servers: Über Direktiven und verschachtelte Blöcke legst du fest, wie Anfragen angenommen, weitergeleitet und ausgeliefert werden. Diese Referenz sammelt die wichtigsten Snippets für den Alltag — von server- und location-Blöcken über Reverse Proxy und TLS bis zu Gzip, Caching, Rate Limiting und Security-Headern. Je nach Direktive gehören die Beispiele in den http-, server- oder location-Kontext deiner /etc/nginx/nginx.conf oder in die Site-Dateien unter sites-available/. Prüfe jede Änderung mit nginx -t und übernimm sie mit einem Reload — so legt dich kein Tippfehler lahm. Den Dienst selbst steuerst du über das separate nginx-CLI-Cheat-Sheet.

Config-Struktur & Kontexte

Top-Level-Struktur der nginx.conf. Der http-Block umschließt die gesamte Web-Konfiguration. include bindet die Site-Dateien ein.

# nginx.conf top-level structure
worker_processes auto;
events {
    worker_connections 1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Einfacher Server-Block (virtueller Host). Legt fest, welchen Port und welche Domain dieser Block bedient.

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/html;
    index index.php index.html;
}

Location-Blöcke matchen die Request-URIs. Exakt (=), Präfix (/), case-insensitiver Regex (~*) und case-sensitiver Regex (~).

location / {
    try_files $uri $uri/ =404;
}
location /api/ {
    proxy_pass http://localhost:3000/;
}
location ~* \.php$ {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

nginx wertet Location-Blöcke in dieser Prioritäts-Reihenfolge aus. Exakte Treffer (=) gewinnen immer.

# Location match priority (highest to lowest):
# 1. = exact match
# 2. ^~ prefix match (stops regex search)
# 3. ~  case-sensitive regex
# 4. ~* case-insensitive regex
# 5. /  prefix match (longest wins)

Virtuelle Hosts (HTTP)

Vollständiger einfacher HTTP-Host mit Document-Root, Index-Dateien und getrennten Log-Dateien.

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example;
    index index.php index.html index.htm;
    access_log /var/log/nginx/example-access.log;
    error_log  /var/log/nginx/example-error.log;
    location / {
        try_files $uri $uri/ =404;
    }
}

Catch-all-Standard-Server-Block. Verwirft Requests ohne passenden server_name (return 444 schließt die Verbindung).

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

Leitet www auf non-www (oder umgekehrt) per permanenter 301-Weiterleitung um.

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

SSL / TLS (HTTPS)

HTTPS-Server-Block mit Let's-Encrypt-Zertifikat. Nur TLS 1.2 und 1.3, schwache Ciphers deaktiviert.

server {
    listen 443 ssl;
    server_name example.com;
    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         HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
}

Leitet allen HTTP-Verkehr auf HTTPS um. Oberhalb des SSL-Server-Blocks platzieren.

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

SSL-Performance- und Sicherheitsoptionen. Der Session-Cache reduziert den Handshake-Overhead. OCSP-Stapling beschleunigt die Zertifikatsprüfung.

ssl_session_cache   shared:SSL:10m;
ssl_session_timeout 1d;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;

listen 443 ssl http2; — Aktiviert HTTP/2 zusätzlich zu SSL. HTTP/2 setzt HTTPS voraus und verbessert die Performance deutlich. Seit nginx 1.25.1 ist die separate Direktive http2 on; empfohlen.

listen 443 ssl http2;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; — HSTS-Header: weist Browser an, diese Domain ein Jahr lang ausschließlich über HTTPS aufzurufen.

add_header Strict-Transport-Security "max-age=31536000" always;

Reverse Proxy

Reverse Proxy: leitet /api/-Requests an eine Backend-App auf Port 3000 weiter. Reicht Client-IP und Protokoll-Header durch.

location /api/ {
    proxy_pass http://localhost:3000/;
    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;
}

Erforderliche Header für WebSocket-Proxying. Innerhalb des Proxy-Location-Blocks ergänzen.

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Proxy-Timeout- und Buffering-Einstellungen. Erhöhe die Timeouts für langsame Backend-Anwendungen.

proxy_connect_timeout 60s;
proxy_send_timeout    60s;
proxy_read_timeout    60s;
proxy_buffering       on;
proxy_buffer_size     16k;
proxy_buffers         4 16k;

Aktiviert das Caching von Proxy-Antworten. Gecachte Antworten kommen direkt von der Platte und entlasten das Backend.

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
location / {
    proxy_cache my_cache;
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404 1m;
    proxy_pass http://backend;
}

PHP-FPM-Integration

Reicht PHP-Requests per Unix-Socket an PHP-FPM weiter. Passe den Socket-Pfad an deine PHP-Version an.

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

Reicht PHP-Requests per TCP auf Port 9000 an PHP-FPM weiter. Nutze TCP, wenn FPM auf einem anderen Host läuft.

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include        fastcgi_params;
}

Front-Controller-Muster: leitet alle Nicht-Datei-Requests an index.php (nötig für Laravel, WordPress usw.).

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Cacht FastCGI-Antworten (PHP-FPM), um die PHP-Ausführung bei wiederholten Requests zu sparen.

fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=php_cache:10m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
location ~ \.php$ {
    fastcgi_cache php_cache;
    fastcgi_cache_valid 200 5m;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

Rewrites & Weiterleitungen

return 301 https://example.com$request_uri; — Permanente Weiterleitung auf eine neue URL. Schneller als rewrite: keine Regex-Verarbeitung.

return 301 https://example.com$request_uri;

return 302 /maintenance.html; — Temporäre Weiterleitung auf eine Wartungsseite.

return 302 /maintenance.html;

Rewrite-Regeln: 'permanent' = 301, 'redirect' = 302. Nutze nach Möglichkeit return; rewrite für Muster-Abgleich.

rewrite ^/old-page$ /new-page permanent;
rewrite ^/blog/(.*)$ /posts/$1 redirect;

rewrite ^/index\.php/?(.*)$ /$1 permanent; — Entfernt index.php per permanenter Weiterleitung aus allen URLs.

rewrite ^/index\.php/?(.*)$ /$1 permanent;

Entfernt www von jeder Domain über eine Regex-Capture-Gruppe. Besser einen separaten Server-Block dafür verwenden.

if ($host ~* ^www\.(.+)$) {
    return 301 https://$1$request_uri;
}

Eigene Fehlerseiten. Der Location-Block liefert die Fehlerseite aus einem bestimmten Pfad aus.

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
    root /var/www/html;
}

Gzip-Komprimierung

Aktiviert Gzip-Komprimierung für gängige textbasierte Content-Types. Innerhalb des http-Blocks platzieren.

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml;

gzip_disable "msie6"; — Deaktiviert Gzip für Internet Explorer 6 (fehlerhafte Gzip-Implementierung). Bedenkenlos einsetzbar.

gzip_disable "msie6";

gunzip on; — Dekomprimiert Gzip-Antworten vom Upstream, bevor sie an Clients ohne Gzip-Unterstützung gehen.

gunzip on;

Statische Dateien & Browser-Caching

Cacht Bilder 30 Tage im Browser. 'immutable' sagt dem Browser, dass er nicht revalidieren muss.

location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

Cacht CSS und JS ein Jahr lang. Funktioniert am besten mit versionierten/gehashten Dateinamen.

location ~* \.(css|js)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Langzeit-Caching für Web-Fonts. Der CORS-Header ist nötig, wenn Fonts cross-origin geladen werden.

location ~* \.(woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public";
    add_header Access-Control-Allow-Origin "*";
}

Unterdrückt 404-Log-Einträge für favicon.ico und robots.txt (sehr häufige Requests).

location = /favicon.ico {
    log_not_found off;
    access_log off;
}
location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
}

Performance-Grundlagen für den http-Block. sendfile ermöglicht Dateiübertragung auf Kernel-Ebene. keepalive reduziert den Verbindungs-Overhead.

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

Security-Header

Essenzielle Security-Header. 'always' stellt sicher, dass der Header auch bei Fehlerantworten gesetzt wird.

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'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always; — Content-Security-Policy-Header. Schränkt ein, welche Ressourcen der Browser laden darf. Passe die Quellen an deine App an.

add_header Content-Security-Policy "default-src 'self';" always;

add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; — Permissions-Policy: deaktiviert Kamera-, Mikrofon- und Geolocation-APIs für diese Seite.

add_header Permissions-Policy "camera=(), microphone=()" always;

server_tokens off; — Verbirgt die nginx-Versionsnummer in HTTP-Response-Headern und auf Fehlerseiten.

server_tokens off;

Sperrt den Zugriff auf versteckte Dateien und Verzeichnisse (z. B. .git, .env, .htaccess). Lässt .well-known für ACME-Challenges zu.

location ~ /\.(?!well-known) {
    deny all;
}

Sperrt den direkten Zugriff auf sensible Dateitypen (Backups, Configs, Logs usw.).

location ~* \.(bak|conf|dist|fla|inc|ini|log|psd|sh|sql|swp)$ {
    deny all;
}

Rate Limiting & Zugriffskontrolle

Rate Limiting: erlaubt max. 10 Requests/Sekunde pro IP, mit Burst bis 20. Überzählige Requests erhalten 503.

# In http block:
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# In server or location block:
limit_req zone=req_limit burst=20 nodelay;

Begrenzt die Zahl gleichzeitiger Verbindungen pro IP auf 10.

limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_conn conn_limit 10;

Erlaubt bestimmte IPs oder Subnetze und sperrt alle anderen. Praktisch für Admin-Bereiche.

allow 192.168.1.0/24;
allow 10.0.0.1;
deny all;

HTTP-Basic-Authentifizierung. .htpasswd erzeugen mit: htpasswd -c /etc/nginx/.htpasswd username

auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;

client_max_body_size 50M; — Setzt die maximal erlaubte Größe des Client-Request-Bodys. Für Datei-Uploads erhöhen.

client_max_body_size 50M;

Load Balancing

Round-Robin-Load-Balancing über drei Backend-Server. Requests werden gleichmäßig verteilt.

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}
server {
    location / {
        proxy_pass http://backend;
    }
}

Least-Connections-Load-Balancing: schickt neue Requests an den Server mit den wenigsten aktiven Verbindungen.

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

IP-Hash-Load-Balancing: leitet dieselbe Client-IP stets an dasselbe Backend (Session-Persistenz).

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

Gewichtetes Load-Balancing mit Backup-Server. Der Backup erhält nur Requests, wenn die primären Server ausgefallen sind.

upstream backend {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=1;
    server 192.168.1.12:8080 backup;
}

Passive Health-Checks: markiert einen Server nach 3 Fehlversuchen als ausgefallen und überspringt ihn dann 30 Sekunden.

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}

Logging

Definiert ein eigenes Log-Format namens 'main' im http-Block. Das ist das Combined-Log-Format.

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

Aktiviert Access-Logging im 'main'-Format und Error-Logging auf Level warn.

access_log /var/log/nginx/access.log main;
error_log  /var/log/nginx/error.log warn;

access_log off; — Deaktiviert Access-Logging für einen Server- oder Location-Block (praktisch für statische Assets).

access_log off;

error_log /var/log/nginx/error.log debug; — Setzt das Error-Log-Level auf debug für ausführliche Ausgabe. Level: debug, info, notice, warn, error, crit, alert, emerg.

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

Access-Log im JSON-Format. Praktisch für Log-Aggregations-Tools (ELK-Stack, Loki usw.).

log_format json_combined escape=json
  '{"time": "$time_local", "remote_addr": "$remote_addr", '
  '"request": "$request", "status": $status, '
  '"bytes": $body_bytes_sent, "ua": "$http_user_agent"}';

Variablen-Referenz

$host — Der Wert des Host-Headers der Anfrage (oder server_name, falls kein Host-Header vorhanden ist).

proxy_set_header Host $host;

$request_uri — Die vollständige originale Request-URI inklusive Query-String, wie vom Client gesendet.

return 301 https://example.com$request_uri;

$uri — Die aktuelle (ggf. umgeschriebene) Request-URI ohne Query-String.

try_files $uri $uri/ /index.php;

$args / $query_string — Der Query-String-Teil der Request-URI (beide Variablen sind gleichwertig).

proxy_pass http://backend?$args;

$remote_addr — Die IP-Adresse des sich verbindenden Clients.

add_header X-Client-IP $remote_addr;

$scheme — Das Schema der Anfrage: 'http' oder 'https'.

proxy_set_header X-Forwarded-Proto $scheme;

$server_name — Der server_name des Server-Blocks, der die Anfrage gematcht hat.

add_header X-Served-By $server_name;

$http_<header> — Zugriff auf jeden Request-Header. Bindestriche werden zu Unterstrichen: $http_user_agent, $http_x_forwarded_for.

if ($http_user_agent ~* 'bot') { return 403; }

Fazit

Eine durchdachte nginx-Konfiguration entscheidet über Performance, Sicherheit und Stabilität deiner Seite. Beginne mit einem sauberen Server-Block, ergänze TLS und Security-Header und aktiviere Gzip sowie Browser-Caching — und schütze öffentliche Endpunkte mit Rate Limiting gegen Missbrauch. Teste jede Änderung mit nginx -t, bevor du neu lädst, und halte deine TLS-Einstellungen mit Bedacht aktuell.

Verwandte Kommandos

  • apache – der klassische Webserver mit modularer Konfiguration
  • caddy – moderner Webserver mit automatischem HTTPS
  • certbot – Let's-Encrypt-Zertifikate ausstellen und erneuern