Nginx läuft auf Millionen Servern — und die meisten sind in der Standardkonfiguration ausgeliefert. Diese 6 Einstellungen machen deinen Nginx zu einem gehärteten Reverse Proxy, der keine unnötigen Informationen preisgibt und aktiv gegen Angriffe schützt.
Voraussetzung: Nginx auf Ubuntu/Debian. Nach jeder Änderung: sudo nginx -t && sudo systemctl reload nginx
01 — Version verstecken: server_tokens off
Standardmäßig sendet Nginx seine Versionsnummer in jedem HTTP-Response-Header. Angreifer nutzen das für gezielte Exploits gegen bekannte Versionen — einfach abstellen.
http {
server_tokens off;
}
sudo nginx -t && sudo systemctl reload nginx
# Prüfen — sollte jetzt nur "nginx" ohne Versionsnummer zeigen:
curl -I https://deine-domain.de | grep Server
02 — Security Headers: HSTS, X-Frame, X-Content-Type
HTTP Security Headers teilen dem Browser mit, wie er sich verhalten soll. Ohne sie sind Nutzer anfällig für Clickjacking, MIME-Sniffing und Man-in-the-Middle-Angriffe.
server {
# HSTS: erzwingt HTTPS für 1 Jahr (für Preload-Liste mind. 1 Jahr)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Verhindert Einbetten der Seite in Iframes (Clickjacking-Schutz)
add_header X-Frame-Options "SAMEORIGIN" always;
# Verhindert MIME-Sniffing durch den Browser
add_header X-Content-Type-Options "nosniff" always;
# Referrer-Informationen einschränken
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Unnötige Browser-Features deaktivieren
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
# Headers prüfen:
curl -I https://deine-domain.de | grep -E "Strict|X-Frame|X-Content|Referrer|Permissions"
# Online-Check (gibt Schulnote):
# https://securityheaders.com/
03 — Rate Limiting: Login-Brute-Force stoppen
Ohne Rate Limiting können Angreifer tausende Login-Versuche pro Minute machen. Nginx begrenzt Anfragen pro IP über limit_req_zone.
# Im http{}-Block von nginx.conf
http {
# Zone "login": max. 5 Anfragen/Sekunde pro IP
# 10m = 10 MB Speicher ≈ 160.000 IP-Adressen
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
# Zone "api": großzügiger für allgemeine API-Calls
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
}
server {
# Login-Endpunkt: burst=10 erlaubt kurze Spitzen, nodelay = sofort 429
location /api/auth/login {
limit_req zone=login burst=10 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8000; # dein Backend
}
# Allgemeine API
location /api/ {
limit_req zone=api burst=50 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8000;
}
}
# Rate Limiting testen (sollte nach ~10 Anfragen 429 zurückgeben):
for i in {1..15}; do
curl -s -o /dev/null -w "%{http_code}\n" https://deine-domain.de/api/auth/login
done
04 — .env & .git blocken
Sensible Dateien sollten nie per HTTP erreichbar sein. return 404 ist besser als deny all (welches 403 zurückgibt und damit verrät, dass die Datei existiert).
server {
# Alle versteckten Dateien/Verzeichnisse blockieren
# Ausnahme: .well-known (benötigt für Let's Encrypt)
location ~ /\.(?!well-known) {
return 404;
}
# Gefährliche Dateiendungen blockieren
location ~* \.(env|log|bak|sql|conf|yaml|yml|ini)$ {
return 404;
}
# Composer/npm-Lockfiles blockieren
location ~* (composer\.(json|lock)|package(-lock)?\.json|yarn\.lock)$ {
return 404;
}
}
# Testen — beide sollten 404 zurückgeben:
curl -o /dev/null -w "%{http_code}" https://deine-domain.de/.env
curl -o /dev/null -w "%{http_code}" https://deine-domain.de/.git/config
05 — Modernes SSL: TLS 1.2/1.3 + Mozilla-Ciphers
TLS 1.0 und 1.1 sind gebrochen und sollten deaktiviert sein. Mozilla veröffentlicht unter ssl-config.mozilla.org eine gepflegte Referenz-Konfiguration.
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on; # Nginx >= 1.25.1 (ältere: "listen 443 ssl http2")
ssl_certificate /etc/letsencrypt/live/deine-domain.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/deine-domain.de/privkey.pem;
# Nur TLS 1.2 und 1.3 (1.0 und 1.1 sind unsicher und abgekündigt)
ssl_protocols TLSv1.2 TLSv1.3;
# Mozilla Intermediate-Konfiguration (Stand 2024)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Session-Caching
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling — beschleunigt Zertifikats-Checking beim Nutzer
ssl_stapling on;
ssl_stapling_verify on;
# Wichtig: chain.pem (nicht fullchain!) für OCSP-Verifikation
ssl_trusted_certificate /etc/letsencrypt/live/deine-domain.de/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
}
💡 Tipp: Auf ssllabs.com/ssltest kannst du deine SSL-Konfiguration kostenlos prüfen — Ziel ist Note A oder A+.
06 — HTTPS erzwingen: 301-Redirect von Port 80
Jede HTTP-Verbindung muss auf HTTPS umgeleitet werden. return 301 ist permanent — Suchmaschinen und Browser merken sich das.
# Server-Block für Port 80
server {
listen 80;
listen [::]:80;
server_name deine-domain.de www.deine-domain.de;
# Let's Encrypt Webroot-Verifikation erlauben
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Alles andere dauerhaft auf HTTPS weiterleiten
location / {
return 301 https://$host$request_uri;
}
}
# Redirect prüfen:
curl -I http://deine-domain.de
# Erwartete Ausgabe:
# HTTP/1.1 301 Moved Permanently
# Location: https://deine-domain.de/
Vollständige Basis-Konfiguration
Alle 6 Maßnahmen in einer übersichtlichen Datei — einfach deine-domain.de ersetzen:
# /etc/nginx/nginx.conf — http{}-Block
http {
server_tokens off;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
}
# /etc/nginx/sites-available/deine-domain.de
# HTTP → HTTPS
server {
listen 80;
listen [::]:80;
server_name deine-domain.de www.deine-domain.de;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
}
# HTTPS
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name deine-domain.de www.deine-domain.de;
ssl_certificate /etc/letsencrypt/live/deine-domain.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/deine-domain.de/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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d; ssl_session_tickets off;
ssl_stapling on; ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/deine-domain.de/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location ~ /\.(?!well-known) { return 404; }
location ~* \.(env|log|bak|sql|conf)$ { return 404; }
location /api/auth/ {
limit_req zone=login burst=10 nodelay;
proxy_pass http://127.0.0.1:8000;
}
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;
}
}
Testen & Validieren
sudo nginx -t && sudo systemctl reload nginx
# Security Headers prüfen:
curl -I https://deine-domain.de | grep -E "Strict|X-Frame|X-Content|Server"
# .env ist gesperrt:
curl -o /dev/null -w "%{http_code}" https://deine-domain.de/.env
# → 404
# SSL-Note prüfen (Ziel: A+):
# https://www.ssllabs.com/ssltest/
# Security Headers Note:
# https://securityheaders.com/
Die Checkliste
- ✅
server_tokens off— Nginx-Version im Header versteckt - ✅ HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy gesetzt
- ✅ Rate Limiting auf Login- und API-Endpunkten aktiv
- ✅
.env,.gitund Konfigurations-Dateien geben 404 zurück - ✅ Nur TLS 1.2/1.3, schwache Cipher-Suites deaktiviert, OCSP Stapling aktiv
- ✅ Port 80 leitet permanent (301) auf HTTPS um
Diese 6 Einstellungen sind in unter 30 Minuten umgesetzt. Kombiniert mit dem Linux-Server-Härtungs-Guide hast du eine solide Sicherheitsbasis für jeden Produktions-Server.

