Veröffentlicht in

nginx – die Wartungsseite, wenn das Backend gerade streikt

Die VM mit dem eigentlichen Webserver muss mal neu gestartet werden – ein Kernel-Update, ein hängender Dienst, was auch immer. Der nginx davor läuft natürlich unbeeindruckt weiter und reicht seine Anfragen brav an ein Backend durch, das gerade nicht mag. Was der Besucher dann sieht, ist ein nackter 502 Bad Gateway. Schöner wäre eine freundliche Wartungsseite – und die kostet uns genau ein paar Zeilen Konfiguration. Der schlanke nginx als Reverse Proxy, den ich in Virtuelle Maschinen – ein Blick hinter die Kulissen schon mal vorgestellt habe, ist hier unser Werkzeug.

Die Grundidee

nginx bringt dafür alles Nötige schon mit: die Direktive error_page. Sie fängt einen Fehlerstatus ab und liefert stattdessen ein Ziel unserer Wahl aus. Genau das brauchen wir.

server {
    # ... server_name, listen, TLS ...

    location / {
        proxy_pass http://192.168.122.10:80;   # unsere VM im virbr0-Netz

        error_page 502 503 504 = @wartung;
    }

    location @wartung {
        root /var/www/wartung;
        rewrite ^ /wartung.html break;
        internal;
    }
}

Das internal; sorgt dafür, dass niemand die Wartungsseite direkt über die URL aufrufen kann – sie ist nur intern über den Umweg erreichbar. Die benannte Location (@wartung) statt einer direkt eingetragenen Datei ist bewusst gewählt: So rutscht die Fehlerbehandlung nicht selbst wieder in einen Proxy-Kontext.

Warum wir proxy_intercept_errors hier nicht brauchen

An dieser Stelle stolpert man gern über einen Ratschlag aus diversen Foren: „Vergiss proxy_intercept_errors nicht!“. Für unseren Fall stimmt das aber gar nicht. Ist die VM weg, kommt gar keine Antwort vom Backend zurück – nginx erzeugt den 502 also selbst. Und selbst erzeugte Fehler fängt error_page ohnehin ab.

Die Direktive brauchen wir erst, wenn das Backend erreichbar ist, aber selbst einen 5xx zurückgibt (die App wirft z. B. ein eigenes 503). Ohne proxy_intercept_errors on; würde nginx diese Antwort samt Body unverändert durchreichen. Für den reinen „VM-Neustart“-Fall ist das aber irrelevant.

Der eigentlich wichtige Teil: die Timeouts

Und jetzt kommt der Punkt, an dem die schöne Wartungsseite sonst ausgerechnet dann unsichtbar bleibt, wenn wir sie am nötigsten haben. Beim Neustart durchläuft die VM nämlich zwei verschiedene Zustände:

  • Connection refused (Port zu, IP noch erreichbar): nginx antwortet sofort mit 502. Der gute Fall.
  • Timeout (IP weg, Pakete versickern): nginx wartet erst den proxy_connect_timeout ab – und der steht per Default auf satten 60 Sekunden.

Eine Minute Ladebalken, bevor überhaupt die Wartungsseite erscheint – das will niemand. Also drehen wir die Timeouts runter:

    location / {
        proxy_pass http://192.168.122.10:80;

        proxy_connect_timeout 2s;
        proxy_read_timeout    5s;
        proxy_send_timeout    5s;

        error_page 502 503 504 = @wartung;
    }

proxy_connect_timeout deckt die Boot- und Shutdown-Phase ab, in der der Verbindungsaufbau scheitert. Die beiden anderen fangen den Fall ab, dass die Verbindung zwar noch steht, die App aber mitten im Restart abbricht.

Bonus: das saubere Signal für Suchmaschinen

Standardmäßig geht die Wartungsseite mit dem ursprünglichen Statuscode raus. Bei einer geplanten Wartung ist ein 503 mit Retry-After das korrekte Signal – dann behalten Suchmaschinen die Seite im Index, statt sie für tot zu halten:

    location @wartung {
        root /var/www/wartung;
        rewrite ^ /wartung.html break;
        internal;
        add_header Retry-After 3600;
    }

Ein letzter Stolperstein für später: Wer im proxy_pass einen Hostnamen statt der IP nutzt, muss wissen, dass nginx den beim Start einmal auflöst und cached. Bei fester libvirt-IP im virbr0-Netz ist das egal – zieht die VM ihre Adresse aber per DHCP, läuft der Proxy nach einem IP-Wechsel ins Leere. Aber das ist eine Geschichte für einen anderen Quick-Tipp.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Datenschutz
Ich, Frank Lüdke, c/o IP-Management #7563 (Wohnort: Deutschland), verarbeite zum Betrieb dieser Website personenbezogene Daten nur im technisch unbedingt notwendigen Umfang. Alle Details dazu in meiner Datenschutzerklärung.
Datenschutz
Ich, Frank Lüdke, c/o IP-Management #7563 (Wohnort: Deutschland), verarbeite zum Betrieb dieser Website personenbezogene Daten nur im technisch unbedingt notwendigen Umfang. Alle Details dazu in meiner Datenschutzerklärung.