techlogia — KI- und Web-Dienstleister Berlin
Nginx härten: 6 Einstellungen für einen sicheren Reverse Proxy

Nginx härten: 6 Einstellungen für einen sicheren Reverse Proxy

23. April 2026

Zurück zum Blog

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, .git und 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.

Nginx härten: 6 Sicherheitseinstellungen | techlogia