ViennaCosa è andato storto?Sicurezza e accessoPrimi passi e gestione

400 Bad Request, e la mia configurazione non era mai attiva

L'accesso da remoto rispondeva solo 400. Tre sospetti scagionati — finché è emerso che Home Assistant quel file non lo legge più da un aggiornamento.

L’appartamento di Vienna era finalmente raggiungibile da fuori — tecnicamente. Dopo un lungo tira e molla la linea aveva di nuovo un indirizzo IPv4 proprio, la porta era aperta, il certificato era valido.

E ogni chiamata rispondeva così:

HTTP/1.1 400 Bad Request
400: Bad Request

Sedici byte di testo puro. Quei sedici byte sono stati l’indizio più utile di tutta la serata.

Prima domanda: chi sta rispondendo?

Tra internet e Home Assistant c’è un reverse proxy. Possono quindi rispondere due programmi — e rispondono in modo diverso.

Il proxy avrebbe mandato una pagina HTML. Un titolo, delle interruzioni di riga, il proprio nome in fondo. Home Assistant manda esattamente questa riga breve.

Rispondeva quindi Home Assistant, e lo faceva di proposito. Il registro diceva anche perché:

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

L’indirizzo era quello giusto e stava nell’intervallo che avevo indicato come attendibile. Home Assistant quell’impostazione semplicemente non la conosceva.

La forma di un messaggio di errore tradisce il mittente prima ancora che se ne legga il contenuto. In una catena di più programmi, non sapere quale ha risposto significa cercare nella casa sbagliata.

Tre sospetti, tre vicoli ciechi

In ordine, perché ognuno era plausibile.

Primo: il pacchetto non viene letto. L’impostazione stava in un file di pacchetto a parte. Forse non veniva caricato affatto. Ho spostato il blocco nella configurazione principale — nessun cambiamento.

Secondo: il riavvio non ha effetto. Sospetto legittimo, perché il mio script di distribuzione segnalava da giorni «di nuovo attivo dopo 5 s», cosa impossibile. Misurato: dopo il comando di riavvio Home Assistant continua a servire per circa 65 secondi prima che il nucleo si fermi davvero. Il mio script aspettava 60 secondi che sparisse, rinunciava — e poi verificava il vecchio processo.

Quello era un errore vero, ed è stato corretto. Solo che non era la causa: con la prova pulita sullo stato dichiarato del nucleo (RUNNING → NOT_RUNNING → RUNNING) il 400 restava.

Terzo: la configurazione non è valida. Nemmeno. Un errore inserito apposta ha dimostrato che il controllore il file lo legge eccome — ha indicato numero di riga e nome del file.

Tre sospetti, tre verifiche, tre assoluzioni. E uno di essi era un errore vero che con il sintomo non c’entrava nulla. Capita.

La causa stava nel codice sorgente

Dalla versione 2026.7.5 la configurazione HTTP migra una sola volta dal YAML a un archivio interno. Da lì in poi vince l’archivio. Nel codice sta in una frase:

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

Da quel momento ogni sezione http: nel YAML è peso morto. Senza avvisi nel registro, e il controllo di configurazione integrato continua a dichiararla valida — verifica la forma, non l’effetto.

L’archivio su questo apparecchio conteneva lo stretto necessario:

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

Nessuna voce sul reverse proxy. Ed ecco la riga che fa male: il created_at è della mattina del 28 agosto — cioè prima che il mio file di pacchetto fosse nemmeno scritto. La migrazione ha fissato lo stato della prima installazione.

Il file è stato inefficace dal primo minuto. Nella mia documentazione c’era scritto comunque che l’impostazione era attiva. Non è mai stato vero. Avevo scritto la configurazione e non avevo mai verificato se funzionasse.

Due ritrovamenti laterali dallo stesso archivio

Porta 80 invece di 8123. Perché questa istanza ascolti su quella porta insolita era una domanda aperta nel progetto dal primo giorno. Sta nell’archivio, non nel YAML. Una domanda aperta fin dall’installazione si è chiusa nell’istante in cui ho letto il file giusto.

Il blocco dopo accessi falliti era disattivato di fabbrica. La soglia stava a -1. Il campo accanto diceva ip_ban_enabled: true e sembrava rassicurante — solo che non contava nulla che potesse far scattare il blocco.

Per un sistema che stava per diventare raggiungibile da internet, è il più importante dei due ritrovamenti. Da solo non sarebbe mai emerso, perché non produce alcun sintomo. Un blocco che non scatta sembra identico a un blocco di cui non c’è mai stato bisogno.

La via giusta — e perché batte un file

L’impostazione non si scrive più in un file ma si applica con tre comandi:

Comando Effetto
leggere mostra cosa vale davvero
impostare deposita la nuova configurazione come provvisoria e riavvia
confermare la rende definitiva

Il passaggio di mezzo è quello interessante. Se la configurazione non viene confermata, dopo un termine torna da sé allo stato precedente.

Un’impostazione che ti chiude fuori, così, non resiste.

È un’idea di progettazione che mi sono annotato: la modifica pericolosa vale dapprima in prova, e tra l’impostare e il confermare ci va la verifica — da fuori, attraverso il proxy, come si presenta un accesso vero.

Da me è andata così: riavvio in 12 secondi, poi da fuori la pagina di accesso con 200 OK e 8503 byte invece di 16, poi conferma. Dopodiché nell’archivio c’era quello che il file si era limitato ad affermare per tutto il tempo.

La sezione http è tolta dal YAML e il file di pacchetto è cancellato — perché nessuno lo scambi più avanti per la verità. Me compreso.

La parte più irritante arriva alla fine

Kapfenberg aveva risolto esattamente questo dieci giorni prima. Documentato, con tanto di script, e con una frase che mi riguardava:

Questo vale allo stesso modo per Vienna e Merano, non appena vengono aggiornate alla 2026.7.5 o più recente. Chi ha lì una sezione http nel YAML dovrebbe verificare cosa c’è in .storage/http.

Vienna è stata portata a una versione più recente tre giorni prima dell’errore. L’avviso stava lì da dieci giorni. Non l’ho letto, e ho invece aperto il codice sorgente di Home Assistant.

Per un errore che potrebbe esistere in un progetto gemello, il primo sguardo va lì — non nel codice sorgente del programma.

Lo script con cui alla fine la cosa si è chiusa viene da lì. Da adattare c’era esattamente una riga: l’intervallo di indirizzi della rete dei componenti aggiuntivi.

E per Merano il punto resta aperto. La stessa sezione inefficace probabilmente sta anche lì; verrà verificata appena quell’istanza girerà su una versione corrispondente. Lo scrivo qui per non lasciarlo di nuovo lì per dieci giorni.

Cosa ti consiglio

  1. Leggi chi risponde. Testo breve o pagina HTML, numero di byte, scelta delle parole — in una catena di proxy e applicazione la forma ti dice dove cercare.
  2. Non verificare che il file ci sia. Verifica che il valore abbia effetto. E fallo dove il programma lo legge davvero.
  3. Metti in conto che il tuo YAML sia stato esautorato. Una migrazione che gira una sola volta rende ogni modifica successiva silenziosamente inefficace. Non riguarda solo Home Assistant.
  4. Applica le impostazioni pericolose con un termine di ritorno e verifica da fuori nel frattempo. Chi si chiude fuori resta davanti alla propria porta.
  5. Cerca nel progetto vicino prima che nel codice sorgente. Se gestisci lo stesso programma due volte, forse l’errore l’hai già risolto una volta.

E quindi?

Il 400 non è stata la parte difficile. L’ho trovato in una sera, e la diagnosi è andata dritta.

Difficile è la parte prima: nel mio progetto stava un file che dal primo giorno credevo facesse qualcosa. Era nella documentazione, era sintatticamente corretto, il controllo lo dichiarava valido — ed era inefficace dal primo minuto.

È lo stesso schema della dieresi che ha ucciso il mio allarme antigelo: lì una funzione di protezione non è scattata per mesi perché puntava a un’entità che non era mai esistita. Entrambe le volte il sistema era a posto. Entrambe le volte l’errore era la mia convinzione.

Configurazione scritta non è configurazione che agisce. La differenza non costa nulla se non una verifica — ed è esattamente quella che mi ero risparmiato, tutte e due le volte.