Nginx still auf HTTP — wie ein 3-Zeilen-Test den Reverse-Proxy entlarvte
Nicht die ausgefallensten Tools, sondern die richtigen.
Incident
Montag, 14:47 Uhr — das Monitoring zeigt grün, aber im Chat schreibt ein Kollege: “Die Anwendung ist wieder über HTTP erreichbar, obwohl der Load-Balancer vorne TLS terminiert.” Ich öffne die Nginx-Konfig, sehe listen 443 ssl, und dennoch bestätigt ein schneller curl -I das Problem: HTTP/1.1 200 OK — kein https://, kein Strict-Transport-Security, kein Upgrade. Der Reverse-Proxy hatte stillschweigend auf HTTP zurückgeschaltet, und niemand hatte es bemerkt, bis ein externes Tool Alarm schlug.
Zwei Stunden später stand der Fix — aber die eigentliche Frage blieb: Warum hat nginx -s reload nicht protestiert, und warum hat das Monitoring geschlafen?
Root Cause
Die Ursache war kein Bug, sondern eine Config-Drift über drei deploys. Ursprünglich stand im Server-Block:
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
Beim letzten Deployment wurde die Zeile ssl_certificate_key auskommentiert — aus Versehen, weil ein Kollege die TLS-Dateien für ein anderes Projekt getestet und den Block zurückgerollt hatte. Nginx startete trotzdem, weil ssl_certificate ohne ssl_certificate_key syntaktisch gültig ist. Statt TLS zu aktivieren, fiel Nginx auf reines HTTP zurück — still, ohne Warnung im Error-Log.
Das Monitoring prüfte nur 200 OK, nicht das Protokoll. Solange der Backend-Healthcheck erfolgreich war, zeigte das Dashboard grün.
Lösung
Der Fix besteht aus zwei Teilen: der Konfigurationskorrektur und einem minimalen Test, der genau diesen Zustand abfängt — zukünftig im CI/CD-Pipeline.
1. Korrektur der TLS-Konfiguration
# /etc/nginx/conf.d/example.com.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# HSTS erzwingen — nur, wenn TLS garantiert funktioniert
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
}
}
# HTTP explizit auf HTTPS umleiten — nicht vergessen!
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$host$request_uri;
}
2. 3-Zeilen-Test für CI/CD
#!/usr/bin/env bash
# test-https.sh — prüft, ob Nginx TLS aktiviert hat
set -euo pipefail
response=$(curl -sI http://localhost/ | head -n 5)
if ! echo "$response" | grep -qi "HTTP/"; then
echo "FEHLER: Keine HTTP-Antwort" >&2
exit 1
fi
# Prüfe TLS-Header im HTTPS-Response
https_response=$(curl -sI https://localhost/ | head -n 5)
if ! echo "$https_response" | grep -qi "HTTP/"; then
echo "FEHLER: HTTPS nicht erreichbar — TLS-Zertifikat oder Port defekt" >&2
exit 1
fi
echo "OK: HTTP und HTTPS antworten"
Dieser Test gehört in die Pipeline, direkt nach dem nginx -s reload. Er kostet 200 Millisekunden und verhindert, dass ein stiller HTTP-Fallback unbemerkt bleibt.
Verifikation
# 1. Manuell prüfen, ob TLS aktiv ist
curl -I https://example.com | head -n 3
# Erwartete Ausgabe:
# HTTP/2 200
# server: nginx
# strict-transport-security: max-age=31536000; includeSubDomains
# 2. Test-Skript ausführen
bash test-https.sh
# Erwartet: "OK: HTTP und HTTPS antworten"
# 3. Nginx-Konfig auf TLS-Schlüssel prüfen
nginx -t 2>&1 | grep -c "ssl_certificate_key"
# Erwartet: 1 (die Zeile muss existieren und auf eine gültige Datei zeigen)
Fazit
Nginx schluckt fehlende TLS-Schlüssel ohne Protest. Deshalb reicht nginx -t und ein grünes Monitoring nicht — das Protokoll muss explizit getestet werden. Mit dem 3-Zeilen-Check kostet die Absicherung weniger als eine Sekunde, und der CI-Pipeline-Gate verhindert, dass ein stiller HTTP-Fallback in Produktion driftet.
Die Lehre: Immer das Verhalten testen, nicht nur die Erreichbarkeit. Ein 200 OK sagt nichts über das Protokoll.
Siehe auch: