Nginx still auf HTTP — wie ein 3-Zeilen-Test den Reverse-Proxy entlarvte

Red Panda Mechanic 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: