WienWas ist schiefgelaufen?Sicherheit und ZugriffEinstieg und Betrieb

400 Bad Request, und meine Konfiguration war nie aktiv

Der Fernzugriff antwortete nur mit 400. Drei Verdächtige fielen durch — bis sich zeigte, dass Home Assistant die Datei seit einem Update gar nicht mehr liest.

Die Wiener Wohnung war von außen endlich erreichbar — technisch. Der Internetanschluss lieferte nach langem Hin und Her wieder eine eigene IPv4-Adresse, die Portfreigabe stand, das Zertifikat war gültig.

Und jeder Aufruf antwortete mit:

HTTP/1.1 400 Bad Request
400: Bad Request

Sechzehn Byte reiner Text. Diese sechzehn Byte waren der wichtigste Hinweis des ganzen Abends.

Erste Frage: Wer antwortet hier eigentlich?

Zwischen Internet und Home Assistant steht ein Reverse Proxy. Zwei Programme können also antworten — und sie antworten verschieden.

Der Proxy hätte eine HTML-Seite geschickt. Überschrift, Zeilenumbrüche, Programmname am Fuß. Home Assistant schickt genau diesen kurzen Text.

Also antwortete Home Assistant selbst, und zwar bewusst. Das Protokoll sagte auch, warum:

A request from a reverse proxy was received from 172.30.33.4,
but your HTTP integration is not set-up for reverse proxies

Die Adresse war die richtige, und sie lag in dem Bereich, den ich als vertrauenswürdig eingetragen hatte. Home Assistant kannte diese Einstellung schlicht nicht.

Die Form einer Fehlermeldung verrät den Absender, noch bevor man ihren Inhalt liest. Wer bei einer Kette aus mehreren Programmen nicht weiß, welches geantwortet hat, sucht im falschen Haus.

Drei Verdächtige, drei Fehlschläge

Der Reihe nach, weil jeder einzelne plausibel war.

Erstens: Das Paket greift nicht. Die Einstellung lag in einer eigenen Paketdatei. Vielleicht wurde die gar nicht eingelesen. Ich verschob den Block in die Hauptkonfiguration — keine Änderung.

Zweitens: Der Neustart greift nicht. Ein berechtigter Verdacht, denn mein eigenes Verteilskript meldete seit Tagen „wieder da nach 5 s“, was unmöglich stimmen konnte. Nachgemessen: Nach dem Neustartbefehl bedient Home Assistant noch rund 65 Sekunden weiter, bevor der Kern tatsächlich stehenbleibt. Mein Skript wartete 60 Sekunden auf das Verschwinden, gab auf — und prüfte danach den alten Prozess.

Das war ein echter Fehler, und er ist behoben. Nur war er nicht die Ursache: Mit sauberem Beweis über den gemeldeten Kernzustand (RUNNING → NOT_RUNNING → RUNNING) blieb der 400 bestehen.

Drittens: Die Konfiguration ist ungültig. Auch nicht. Ein absichtlich eingebauter Fehler zeigte, dass der Prüfer die Datei sehr wohl liest — er nannte Zeilennummer und Dateinamen.

Drei Verdächtige, drei Prüfungen, drei Freisprüche. Und einer davon war ein echter Fehler, der mit dem Symptom nichts zu tun hatte. Die gibt es.

Die Ursache stand im Quelltext

Seit Fassung 2026.7.5 wandert die HTTP-Konfiguration einmalig aus der YAML in einen internen Speicher. Danach gewinnt dieser Speicher. Im Quelltext steht es in einem Satz:

YAML config is only migrated once. Subsequent boots will ignore YAML and use the store exclusively.

Ab da ist jede http:-Sektion in der YAML totes Gewicht. Ohne Warnung im Protokoll, und die eingebaute Konfigurationsprüfung meldet sie weiterhin als gültig — sie prüft die Form, nicht die Wirkung.

Der Speicher auf diesem Gerät enthielt genau das Nötigste:

"server_port": 80,
"login_attempts_threshold": -1,
"ip_ban_enabled": true,
"created_at": "2026-08-28T06:41:36"
"yaml_migration_done": true

Keine Angabe zum Reverse Proxy. Und jetzt die Zeile, an der es wehtut: Das created_at liegt am Vormittag des 28. August — also bevor meine Paketdatei überhaupt geschrieben wurde. Die Migration hat den Zustand aus der Ersteinrichtung festgeschrieben.

Die Datei war von der ersten Minute an wirkungslos. In meiner eigenen Dokumentation stand trotzdem, die Einstellung sei gesetzt. Das war nie wahr. Ich hatte die Konfiguration geschrieben und nie nachgemessen, ob sie wirkt.

Zwei Nebenbefunde aus demselben Speicher

Port 80 statt 8123. Warum diese Instanz auf dem ungewöhnlichen Port lauscht, stand seit dem ersten Tag ungeklärt im Projekt. Es steht im Speicher, nicht in der YAML. Eine Frage, die seit der Einrichtung offen war, war in dem Augenblick beantwortet, in dem ich die richtige Datei las.

Die Sperre nach Fehlanmeldungen war ab Werk aus. Der Schwellenwert stand auf -1. Das Feld daneben hieß ip_ban_enabled: true und sah beruhigend aus — nur zählte nichts mit, was die Sperre hätte auslösen können.

Für ein System, das gerade aus dem Internet erreichbar werden sollte, ist das der wichtigere der beiden Funde. Er wäre nie aufgefallen, denn er erzeugt kein Symptom. Eine Sperre, die nicht greift, sieht aus wie eine Sperre, die nie gebraucht wurde.

Der richtige Weg — und warum er klüger ist als eine Datei

Die Einstellung wird nicht mehr in eine Datei geschrieben, sondern über drei Befehle gesetzt:

Befehl Wirkung
lesen zeigt, was wirklich gilt
setzen legt die neue Konfiguration als vorläufig ab und startet neu
bestätigen schreibt sie endgültig fest

Der mittlere Schritt ist der interessante. Wird die Konfiguration nicht bestätigt, fällt sie nach einer Frist von selbst auf den alten Stand zurück.

Eine Einstellung, die einen aussperrt, hält sich so nicht.

Das ist eine Entwurfsidee, die ich mir gemerkt habe: Die gefährliche Änderung gilt zunächst auf Probe, und zwischen Setzen und Bestätigen gehört die Prüfung — von außen, durch den Proxy, so wie ein echter Zugriff aussieht.

Bei mir sah das so aus: Neustart in 12 Sekunden, dann von außen die Anmeldeseite mit 200 OK und 8503 Byte statt 16, dann bestätigen. Danach stand in dem Speicher, was die Datei die ganze Zeit nur behauptet hatte.

Die http-Sektion ist aus der YAML entfernt und die Paketdatei gelöscht — damit sie niemand später für die Wahrheit hält. Mich eingeschlossen.

Das Ärgerlichste kommt zum Schluss

Kapfenberg hatte genau das zehn Tage vorher gelöst. Dokumentiert, mit Skript, und mit einem Satz, der mich betraf:

Das betrifft Wien und Meran genauso, sobald dort auf 2026.7.5 oder neuer aktualisiert wird. Wer dort eine http-Sektion in der YAML hat, sollte prüfen, was in .storage/http steht.

Wien wurde drei Tage vor dem Fehler auf eine neuere Fassung gehoben. Der Hinweis lag zehn Tage bereit. Ich habe ihn nicht gelesen und stattdessen den Quelltext von Home Assistant aufgeschlagen.

Bei einem Fehler, den es in einem Schwesterprojekt geben könnte, gehört der erste Blick dorthin — nicht in den Quelltext der Software.

Das Skript, mit dem die Sache am Ende erledigt war, stammt von dort. Angepasst war genau eine Zeile: der Adressbereich des Add-on-Netzes.

Und für Meran steht der Punkt bis heute offen. Dieselbe wirkungslose Sektion liegt dort vermutlich auch — geprüft wird sie, sobald die Instanz auf eine entsprechende Fassung gehoben ist. Ich schreibe das hier hin, damit ich es nicht wieder zehn Tage liegen lasse.

Was ich dir rate

  1. Lies, wer antwortet. Kurzer Text oder HTML-Seite, Zeichenzahl, Wortwahl — bei einer Kette aus Proxy und Anwendung sagt dir die Form, wo du suchen musst.
  2. Prüf nicht, ob die Datei da ist. Prüf, ob der Wert wirkt. Und zwar dort, wo die Software ihn tatsächlich ausliest.
  3. Rechne damit, dass deine YAML entmachtet wurde. Eine Migration, die genau einmal läuft, macht jede spätere Änderung still wirkungslos. Das betrifft nicht nur Home Assistant.
  4. Setz gefährliche Einstellungen mit Rückfallfrist und prüf dazwischen von außen. Wer sich aussperrt, steht sonst vor der eigenen Wohnung.
  5. Such erst im Nachbarprojekt, dann im Quelltext. Wenn du dieselbe Software zweimal betreibst, hast du den Fehler womöglich schon einmal gelöst.

Und was heißt das?

Der 400 war nicht das Schwierige. Er war an einem Abend gefunden, und die Diagnose war geradlinig.

Schwierig ist der Teil davor: In meinem Projekt lag eine Datei, von der ich vom ersten Tag an geglaubt habe, sie tue etwas. Sie stand in der Dokumentation, sie war syntaktisch korrekt, die Prüfung meldete sie als gültig — und sie war ab der ersten Minute wirkungslos.

Das ist dasselbe Muster wie bei dem Umlaut, der meinen Frostalarm getötet hat: Dort hat eine Schutzfunktion monatelang nicht ausgelöst, weil sie auf eine Entität zeigte, die es nie gab. Beide Male war das System in Ordnung, beide Male war meine Annahme der Fehler.

Geschriebene Konfiguration ist keine wirkende Konfiguration. Der Unterschied kostet nichts außer einer Nachmessung — und genau die hatte ich mir beide Male gespart.