UniFi OS und Protect hinter Caddy: Wenn die Oberfläche im Ladebalken hängen bleibt
Caddy 2.11.6, UniFi Protect und ein Reverse Proxy mit gültigem TLS-Zertifikat: Eigentlich sollte der Zugriff auf die UniFi-Oberfläche problemlos funktionieren. In meinem Test blieb die Oberfläche jedoch beim Laden hängen.
Ergebnisse aus meiner konkreten Installation, Ursache nicht isoliert nachgewiesen.
AUSGANGSLAGE
Das Problem: UniFi bleibt beim Laden hängen
Die Browser-Konsole zeigte fehlgeschlagene WebSocket-Verbindungen mit HTTP-Status 500. Die Ursache lag offenbar nicht an der Firewall oder an der grundsätzlichen Erreichbarkeit des Cloud Keys. Entscheidend war eine Besonderheit beim Zusammenspiel von Caddy, HTTPS-Upstreams und den von UniFi verwendeten WebSockets.
In diesem Beitrag dokumentiere ich die Fehlersuche, die Messungen und die Konfiguration, mit der UniFi Protect anschließend wieder funktionierte.
Ich betreibe UniFi Protect auf einem Cloud Key im Heimnetz. Der Zugriff sollte über meinen zentralen Caddy-Reverse-Proxy und ein gültiges Wildcard-Zertifikat erfolgen.
Die Ausgangssituation war zunächst unspektakulär: Die Anmeldeseite wurde aufgerufen, das UniFi-Logo erschien, anschließend blieb die Oberfläche jedoch dauerhaft im Ladebildschirm hängen.
Ein Blick in die Browser-Konsole brachte den entscheidenden Hinweis. Die WebSocket-Verbindungen zu folgenden Endpunkten schlugen fehl:
/api/ws/system/api/ws/webrtc/local
Insbesondere die Verbindung zu /api/ws/system ist für den Aufbau der UniFi-Oberfläche relevant. Funktioniert dieser WebSocket nicht, kann die Oberfläche beim Laden hängen bleiben.
Ein direkter Zugriff auf den Cloud Key funktionierte dagegen. Damit war klar: Das Problem trat beim Zugriff über den Reverse Proxy auf.
AUSGANGSKONFIGURATION
Die Verbindung im Überblick
Die Verbindung verlief über folgende Kette: Browser → Caddy → UniFi Protect auf dem Cloud Key
Caddy stellte die Verbindung zum Backend über HTTPS her. Da der Cloud Key ein selbstsigniertes Zertifikat verwendete, war die Zertifikatsprüfung für den Upstream deaktiviert.
Das folgende Beispiel verwendet nvr.home.example als Hostnamen und 192.0.2.10 als beispielhafte IP-Adresse des Cloud Keys. Die ursprüngliche Konfiguration sah so aus:
@nvr host nvr.home.example
handle @nvr {
reverse_proxy https://192.0.2.10 {
transport http {
tls_insecure_skip_verify
}
}
}Auf den ersten Blick ist das ein übliches Muster für einen HTTPS-Upstream mit selbstsigniertem Zertifikat. Trotzdem funktionierte der Zugriff über Caddy nicht.
FEHLERSUCHE
Was ich ausschließen konnte
Mein erster Verdacht war die Firewall. Wenn eine Weboberfläche nicht vollständig lädt, liegt der Gedanke an eine blockierte Verbindung schließlich nahe. Die weiteren Tests sprachen allerdings gegen diese Erklärung.
Der Cloud Key war erreichbar
Ein curl-Aufruf von Caddy zum Cloud Key funktionierte. Die grundlegende Netzwerkverbindung zwischen Reverse Proxy und Backend war also vorhanden.
Die WebSockets scheiterten sofort
Die fehlgeschlagenen WebSocket-Anfragen endeten nach ungefähr 10 bis 20 Millisekunden. Das passte nicht zu einer typischen Firewall-Blockade, bei der eine Verbindung häufig in einen Timeout läuft. Ein schneller Fehler ist zwar kein endgültiger Beweis gegen die Firewall, machte diese Erklärung aber deutlich unwahrscheinlicher.
Das Access-Log zeigte HTTP 500
Im Caddy-Access-Log war für die betroffenen Anfragen der Status 500 Internal Server Error zu sehen. Die Antwort enthielt außerdem Server: nginx und einen JSON-Response-Body.
Das deutete darauf hin, dass die Fehlerantwort vom Backend stammte und von Caddy weitergereicht wurde. Der Server-Header allein ist allerdings kein absoluter Beweis für den Ursprung einer Antwort. Zusammen mit den übrigen Messungen war er ein weiterer Hinweis auf ein Problem innerhalb der UniFi-Anwendung beziehungsweise ihrer HTTP-Verarbeitung.
Direkt gegen Protect funktionierte der WebSocket
Der wichtigste Vergleich war der direkte Test gegen den Cloud Key. Mit einem gültigen Cookie erhielt ich bei einem WebSocket-Handshake den erwarteten Status HTTP/1.1 101 Switching Protocols.
Das funktionierte auch dann, wenn ich beim direkten Zugriff den Hostnamen nvr.home.example, den passenden Origin und die entsprechenden Browser-Header verwendete. Der WebSocket-Handshake funktionierte also grundsätzlich.
Andere Headeränderungen halfen nicht
Ich testete außerdem Änderungen an den X-Forwarded-*-Headern sowie die erzwungene Verwendung von HTTP/1.1. Keine dieser Änderungen allein beseitigte den Fehler.
Damit blieb eine andere mögliche Ursache im Fokus: der Host-Header, den Caddy an den HTTPS-Upstream weitergab.
VERMUTETE URSACHE
Der Host-Header passt nicht zum Origin
Mit Caddy 2.11 hat sich das Verhalten beim Reverse Proxy zu HTTPS-Upstreams geändert. Caddy passt den Host-Header standardmäßig an den Upstream an, damit er zum Ziel der HTTPS-Verbindung passt.
Diese Änderung ist dokumentiert und hat bereits bei anderen Reverse-Proxy-Konfigurationen zu Problemen geführt. In der Caddy-Community wird deshalb unter anderem empfohlen, den ursprünglichen Hostnamen ausdrücklich weiterzugeben. Für meine Konfiguration ergibt sich daraus folgende mögliche Situation:
| Header | Erwarteter Wert |
|---|---|
Host | 192.0.2.10 |
Origin | https://nvr.home.example |
Der Browser greift über den öffentlichen Hostnamen zu. Wenn Caddy beim Weiterleiten den Host-Header auf die Backend-IP setzt, während der Origin weiterhin den ursprünglichen Hostnamen enthält, stimmen beide Angaben nicht mehr überein.
Meine Vermutung: UniFi prüft beim WebSocket-Handshake die Kombination aus Host und Origin. Ein Widerspruch könnte dazu führen, dass das Backend den Handshake mit HTTP 500 ablehnt. Diese Erklärung passt zu meinen Messungen: Beim direkten Zugriff mit passendem Host und Origin erhielt ich HTTP 101. Über Caddy schlug der Handshake dagegen fehl.
Host = IP-Adresse und Origin = Domainname nicht noch einmal isoliert nachgestellt. Die Ursache ist deshalb plausibel und durch die anschließende Lösung gestützt, aber nicht abschließend bewiesen.Ein weiterer Stolperstein: HTTP/2 zum Backend
Caddy verwendet bei TLS-Upstreams seit Version 2.10 standardmäßig auch HTTP/2. UniFi unterstützt jedoch nicht in jeder betroffenen Konstellation WebSockets über HTTP/2. Die Caddy-Community dokumentiert deshalb einen weiteren Lösungsansatz: HTTP/1.1 für die Verbindung zum Backend ausdrücklich festlegen.
In meinem Test beseitigte die Änderung am Host-Header das Problem. Ob versions 1.1 in meiner konkreten Installation zusätzlich erforderlich ist, habe ich nicht einzeln getestet.
LÖSUNG
Host-Header ausdrücklich setzen
Die entscheidende Änderung bestand darin, den ursprünglichen Hostnamen explizit an das Backend weiterzugeben. Meine funktionierende Konfiguration lautet:
@nvr host nvr.home.example
handle @nvr {
reverse_proxy https://192.0.2.10 {
header_up Host {host}
transport http {
tls_insecure_skip_verify
versions 1.1
}
}
}Die entscheidende Zeile ist header_up Host {host}. Damit verwendet Caddy beim Weiterleiten den Hostnamen aus der ursprünglichen Client-Anfrage, anstatt den Host-Header an die Backend-Adresse anzupassen. Die Zeile versions 1.1 erzwingt HTTP/1.1 für die Verbindung zum Backend.
WENN ES NICHT REICHT
Was tun, wenn es weiterhin nicht funktioniert?
Je nach UniFi-Version, Gateway und Reverse-Proxy-Konfiguration können weitere Anpassungen erforderlich sein. In Community-Beiträgen werden unter anderem diese Varianten diskutiert.
Host mit Port
header_up Host {hostport}{hostport} berücksichtigt zusätzlich einen gegebenenfalls vorhandenen Port.
Host des Upstreams
header_up Host {upstream_hostport}{upstream_hostport} bezieht sich auf den Upstream, {host} dagegen auf den Hostnamen der ursprünglichen Anfrage.
Origin entfernen
header_up Origin ""Das sollte nicht die erste Maßnahme sein. Der Origin-Header dient der Herkunftsprüfung. Ihn zu entfernen, kann Sicherheitsprüfungen verändern und sollte nur nach gezielter Prüfung der konkreten Installation erfolgen.
Welche Variante funktioniert, hängt davon ab, welche Werte UniFi erwartet. Deshalb sollte man diese Einstellungen nicht blind austauschen, sondern gezielt testen. Die Community-Erfahrungen zeigen außerdem, dass eine wieder ladende Oberfläche nicht zwangsläufig bedeutet, dass alle Funktionen – etwa Live-Kamerabilder – anschließend funktionieren.
EIGENE FEHLERSUCHE
Tipps für die eigene Fehlersuche
Wer ein ähnliches Problem untersucht, sollte die Fehlerquelle möglichst systematisch eingrenzen.
Caddy-Access-Log aktivieren
Mit dem log-Block im Site-Abschnitt lassen sich Status und Dauer der Anfragen nachvollziehen. Besonders hilfreich ist es, die fehlgeschlagenen WebSocket-Anfragen separat zu betrachten.
Bei Bedarf Debug-Logging einschalten
Caddys globaler debug-Schalter kann bei der Fehlersuche helfen. Dabei können sensible Header sichtbar werden.
WebSocket-Handshake gezielt testen
Ein WebSocket-Handshake lässt sich beispielsweise mit curl und den entsprechenden Upgrade-Headern nachstellen. Ein erfolgreicher Handshake sollte mit HTTP 101 antworten.
curl -sk -i --http1.1 \
https://192.0.2.10/api/ws/system \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Host: nvr.home.example" \
-H "Origin: https://nvr.home.example" \
-H "Cookie: TOKEN=<dein-token>"Das Beispiel verwendet bewusst Platzhalter für Hostname, IP-Adresse und Token. Der Test muss mit einem gültigen Authentifizierungstoken und den zur Installation passenden Headern durchgeführt werden. Ein einfacher curl-Aufruf ohne diese Angaben ist kein aussagekräftiger WebSocket-Test.
Host und Origin einzeln variieren
Für eine belastbare Ursachenanalyse sollten Host, Origin und die verwendete HTTP-Version einzeln verändert werden. So lässt sich feststellen, welche Änderung tatsächlich für den Erfolg verantwortlich ist.
Zugangsdaten niemals veröffentlichen
Der Cookie-Wert TOKEN=... kann einen gültigen Login repräsentieren. Wer Logs oder Befehle in Foren veröffentlicht, muss solche Werte vollständig entfernen. Falls ein echter Sitzungstoken versehentlich veröffentlicht wurde, sollte die Sitzung beendet beziehungsweise der Token ungültig gemacht werden.
FAZIT
Nicht zwangsläufig die Firewall
Wenn UniFi OS oder Protect hinter Caddy im Ladebildschirm hängen bleibt, muss nicht zwangsläufig die Firewall die Ursache sein.
In meinem Test funktionierte der direkte Zugriff auf den Cloud Key, während die WebSocket-Verbindungen über Caddy mit HTTP 500 scheiterten. Die explizite Weitergabe des ursprünglichen Hostnamens mit header_up Host {host} brachte die Oberfläche wieder zum Laufen.
Die Messungen sprechen dafür, dass das Verhalten des Host-Headers bei HTTPS-Upstreams in Verbindung mit der WebSocket-Verarbeitung von UniFi eine Rolle spielte. Da ich die vermutete Host-/Origin-Abweichung nicht separat reproduziert habe, bleibt die genaue Ursache allerdings offen.
Mein Tipp: Bei ähnlichen Problemen zuerst die WebSocket-Anfragen im Browser und im Caddy-Access-Log untersuchen. Anschließend den Host-Header und die HTTP-Version zum Backend gezielt testen, bevor Firewall-Regeln oder andere Netzwerkkomponenten verändert werden.
QUELLEN
Quellen und Links
Netzwerk, Reverse Proxy oder Hosting-Umgebung planen?
RDDZ unterstützt bei Planung, Umsetzung und Fehlersuche in Netzwerken und Hosting-Umgebungen – von der Konzeption bis zum laufenden Betrieb.


