Dasselbe Schloss, deine eigene Tür
Die Home-Assistant-App über Cloudflare, an ein Zertifikat gebunden setzt das Schloss an Cloudflares Edge. Das Telefon hat ein Zertifikat, Cloudflare prüft es, und alles ohne eins erreicht das Haus nie. Es funktioniert, es kostet nichts, und es heißt, dass Cloudflare das Tor hält und den Verkehr im Vorbeigehen liest.
Das hier ist dasselbe Schloss mit der Tür im eigenen Haus. nginx oder Caddy prüft das Zertifikat. Deine eigene Zertifizierungsstelle stellt es aus. Niemand sonst ist im Weg. Die Anmeldeseite bleibt für alles ohne Zertifikat unsichtbar, also ändert sich nicht, ob Fremde hineinkommen. Es ändert sich, wer die Arbeit macht und wer das Risiko trägt, wenn etwas schiefgeht.
Alles hier wurde gebaut und getestet: nginx 1.30.5 und Caddy 2.11.7 in Podman, Zertifikate von step-ca 0.30.2, jeder Proxy angesprochen ohne Zertifikat, mit einem guten Zertifikat, einem widerrufenen und einem WebSocket-Upgrade, über IPv4 und IPv6. Das Backend im Test war ein kleiner Platzhalter, der die empfangenen Header zurückgibt, also zeigen die Ergebnisse genau, was Home Assistant übergeben würde.
Was sich ändert, wenn Cloudflare wegfällt
| Cloudflare-Variante | nginx oder Caddy zu Hause | |
|---|---|---|
| Wer das Zertifikat prüft | Cloudflares Edge, mit einer WAF-Regel | Dein Proxy, während des TLS-Handshakes |
| Zertifizierungsstelle | Von Cloudflare verwaltet | Deine eigene, step-ca für Client-Zertifikate |
| Wer den Verkehr entschlüsselt sieht | Cloudflare, dann Home Assistant | Dein Proxy und Home Assistant, sonst niemand |
| Weg von außen hinein | Ein Tunnel, nur ausgehend | Eine Portweiterleitung zum Proxy |
| Ein DDoS landet bei | Cloudflare | Deiner Leitung zu Hause |
| Die Verbindung eines Fremden kostet | Zu Hause nichts | Einen TLS-Handshake am Proxy, nie eine Anmeldung bei Home Assistant |
| IPv4 und IPv6 | Beides, Cloudflare beantwortet jedes | Beides, wenn die Leitung beides hat und der Proxy auf beidem lauscht |
| Widerruf | Eingebaut, von der Regel geprüft | nginx prüft eine CRL; Caddy führt eine Erlaubnisliste |
| Wenn der Anbieter ausfällt | Kein Fernzugriff | Nichts ändert sich |
| Laufende Kosten | Kostenloser Tarif | Kostenlos, plus deine Zeit für die CA |
Die zwei Zeilen, auf die es am meisten ankommt, ziehen in entgegengesetzte Richtungen. Niemand sonst liest deinen Verkehr oder kann dich abschalten, und das ist Souveränität, der ganze Grund, es zu tun. Aber die Portweiterleitung ist zurück, also treffen Scans und Fluten deine Leitung statt Cloudflares. Der Unterschied zur einfachen Portweiterleitung aus dem Vergleich im Cloudflare-Beitrag ist, was sie bei der Ankunft vorfinden: einen Proxy, der die Verbindung im Handshake fallen lässt, nicht die Anmeldeseite von Home Assistant.
Sei dir klar darüber, was das bringt und was nicht. Dein eigener Reverse Proxy gibt dir überhaupt keinen DDoS-Schutz. mTLS hält einen Fremden davon ab hineinzukommen. Es tut nichts dagegen, dass ein Fremder deine Leitung zuschüttet, und eine Flut braucht kein Zertifikat, um sie zu füllen. Cloudflare fängt das für dich in jedem Tarif ab, den kostenlosen eingeschlossen1. Hier ist niemand davor, der es abfängt, also nimmt eine Flut, die groß genug ist, das ganze Haus vom Netz, und der Proxy kann nur Verbindungen abweisen, die er überhaupt zu sehen bekommt.
nginx oder Caddy
Beide erledigen die Aufgabe. Sie unterscheiden sich an einer Stelle, auf die es ankommt. Beim Widerruf.
| nginx 1.30.5 | Caddy 2.11.7 | |
|---|---|---|
| Kein Zertifikat | Handshake gelingt, 400 No required SSL certificate was sent | Handshake abgewiesen, curl Exit 55 |
| Widerrufenes Zertifikat | Abgewiesen, 400 The SSL certificate error | Direkt hereingelassen, wenn nur die CA geprüft wird |
| Widerrufsmethode | Eine CRL, über die ganze Kette geprüft | Eine Erlaubnisliste von Zertifikaten, der Verifier leaf |
| WebSocket für die App | Braucht gesetzte Upgrade- und Connection-Header | Funktioniert ohne zusätzliche Konfiguration |
| IPv6 | Nur mit zusätzlichem listen [::]:443 | Lauscht standardmäßig auf beidem |
| Konfiguration | Etwa 30 Zeilen | Etwa 15 Zeilen |
Der Widerruf ist das, worüber du entscheiden musst. Caddys Doku nennt genau einen Verifier, der mit dem Standard-Caddy ausgeliefert wird, leaf, und der prüft, ob das Zertifikat „is one of a defined set of permitted certificates“2. Es gibt keine CRL und kein OCSP für Client-Zertifikate. Lass Caddy nur auf die CA schauen, und ein widerrufenes Telefon spaziert herein. Genau das hat der Test gezeigt: phone2, eine Stunde vorher widerrufen, bekam eine 200. Die Erlaubnisliste schließt die Lücke. Sie ist auch eine Liste, die du von Hand pflegst, solange du das betreibst.
Die nginx-Konfiguration
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ha.example.com;
ssl_certificate /etc/nginx/pki/server-chain.pem;
ssl_certificate_key /etc/nginx/pki/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
# The mTLS half: only certificates from our CA, and not revoked.
ssl_client_certificate /etc/nginx/pki/client-ca.pem;
ssl_verify_client on;
ssl_verify_depth 2;
ssl_crl /etc/nginx/pki/crl-chain.pem;
location / {
proxy_pass http://192.0.2.10:8123;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
| Zeile | Warum sie da ist |
|---|---|
listen [::]:443 ssl | Ohne sie spricht nginx nur IPv4, und ein Telefon in einem IPv6-Netz hat keinen Landeplatz |
ssl_verify_client on | Kein Zertifikat, kein Zutritt. optional würde die Anfrage durchlassen, damit später über sie geurteilt wird |
ssl_verify_depth 2 | step-ca signiert von einer Zwischen-CA aus, also ist die Kette Telefon, Zwischen-CA, Root. Die Voreinstellung 1 hört zu früh auf3 |
ssl_client_certificate | Die Zwischen-CA und der Root in einer Datei |
ssl_crl | Die CRL von step-ca und eine Root-CRL in einer Datei. nginx prüft jedes Glied der Kette, also braucht es beide |
Upgrade und Connection | Die App spricht über einen WebSocket mit Home Assistant. Ohne diese Zeilen lädt die App und bleibt dann einfach stehen |
Die CRL-Datei ist der Teil, der Sorgfalt braucht, und alles dazu steht in Client-Zertifikate von step-ca: warum die CRL von step-ca abgewiesen wird, bis eine Vorlage ihren Geltungsbereich richtet, warum nginx auch eine CRL vom Root will, und der Timer, der sie auffrischt, bevor sie abläuft. Lass das weg, und nginx sperrt jedes Telefon aus, auch die guten.
Was der Test zurückbekam:
| Client | IPv4 | IPv6 |
|---|---|---|
| Kein Zertifikat | 400 No required SSL certificate was sent | 400 |
| phone1 | 200, X-Forwarded-For: 127.0.0.1 | 200, X-Forwarded-For: ::1 |
| phone2, widerrufen | 400 The SSL certificate error | 400 |
| phone1, WebSocket-Upgrade | 200, Upgrade: websocket durchgereicht | 200, Upgrade: websocket durchgereicht |
Bevor listen [::]:8443 drin war, kam die IPv6-Anfrage gar nicht erst zustande. curl Exit 7.
Die Caddy-Konfiguration
ha.example.com {
tls /etc/caddy/pki/server-chain.pem /etc/caddy/pki/server.key {
client_auth {
mode require_and_verify
trust_pool file /etc/caddy/pki/client-ca.pem
verifier leaf {
file /etc/caddy/pki/allowed-phones.pem
}
}
}
reverse_proxy 192.0.2.10:8123
}
require_and_verify heißt, das Zertifikat muss da sein und muss zur CA führen2. verifier leaf ist der Widerruf. Nur die Zertifikate in allowed-phones.pem kommen hinein, also heißt ein Telefon widerrufen, sein Zertifikat aus dieser Datei zu nehmen und neu zu laden.
| Client | IPv4 | IPv6 |
|---|---|---|
| Kein Zertifikat | Handshake abgewiesen | Handshake abgewiesen |
| phone1, auf der Liste | 200, X-Forwarded-For: 127.0.0.1 | 200, X-Forwarded-For: ::1 |
| phone2, widerrufen, nur CA-Prüfung | 200 | |
| phone2, nicht auf der Liste | Handshake abgewiesen | Handshake abgewiesen |
| phone1, WebSocket-Upgrade über HTTP/1.1 | 200, Upgrade: websocket durchgereicht |
Die Erlaubnisliste hat eine eigene Falle, und der Test ist geradewegs hineingelaufen. Die Liste enthält exakte Zertifikate, keine Namen. Stell das Zertifikat eines Telefons neu aus, und das neue steht nicht auf der Liste. phone1 wurde mitten im Test neu ausgestellt, und Caddy wies es mit tls alert bad certificate ab, bis die Liste neu gebaut war. Jede Erneuerung sind jetzt zwei Arbeiten. Das Zertifikat ausstellen, dann die Liste aktualisieren. Vergiss die zweite, und das Telefon ist für nix und wieder nix ausgesperrt.
Caddy loggt beim Start außerdem enabling strict SNI-Host enforcement because TLS client auth is configured. Das ist Caddy, das das Richtige tut: eine Anfrage muss im Handshake denselben Hostnamen verlangen wie in der HTTP-Anfrage, damit niemand die Zertifikatsprüfung für eine Seite besteht und dann nach einer anderen fragt.
Die Seite von Home Assistant
Dieselben Einstellungen wie in der Cloudflare-Variante, auf einen anderen Proxy gerichtet. Unter Settings → System → Network → HTTP server4:
| Einstellung | Wert | Warum |
|---|---|---|
| Trust X-Forwarded-For | An | Ohne sie lehnt Home Assistant weitergeleitete Anfragen ab |
| Trusted proxies | Nur die Adresse des Proxys, IPv4 und IPv6, zum Beispiel 192.0.2.20 und 2001:db8::20 | Nur diese Kiste darf behaupten, für jemanden weiterzuleiten |
| Enable IP banning | An | Sperren treffen die echte Client-Adresse, die der Proxy weitergibt |
| Home Assistant URL, Internet | https://ha.example.com | Die App übernimmt das als ihre externe URL |
Trag die IPv6-Adresse des Proxys genauso ein wie seine IPv4-Adresse. Der Test zeigt, dass nginx über IPv6 ::1 als Client-Adresse weiterreicht, und im Betrieb reicht es weiter, wo auch immer das Telefon herkam. Vertraut Home Assistant nur der IPv4-Adresse des Proxys, wird jede Anfrage abgewiesen, die es vom Proxy über IPv6 erreicht.
Das Telefon
Von step-ca braucht das Telefon drei Dateien. Alles dazu steht im Beitrag über Client-Zertifikate mit step-ca, und die Einrichtung der CA selbst in Deine eigene Zertifizierungsstelle.
| Datei | Installieren als |
|---|---|
| Root-Zertifikat | CA-Zertifikat |
| Zwischenzertifikat | CA-Zertifikat |
| Die PKCS12-Datei des Telefons | VPN- und App-Benutzerzertifikat5 |
Das Serverzertifikat am Proxy kommt auch von step-ca. Die Home-Assistant-App für Android vertraut CA-Zertifikaten, die der Benutzer installiert6, also akzeptiert sie es, und das ist ein zweites Schloss, kein Ärgernis. Ein Gerät, das deinen Root nicht hat, vertraut dem Server nicht, bevor die Frage nach seinem eigenen Zertifikat überhaupt aufkommt.
Dann stell die Verbindungssicherheit der App auf Most secure, genau wie in der Cloudflare-Variante7.
Wo es an seine Grenzen kommt
| Fall | Was passiert | Was tun |
|---|---|---|
| CRL läuft ab | nginx weist jedes Telefon ab | Per Timer auffrischen, wie im step-ca-Beitrag |
| Root-CRL läuft ab | Dasselbe, ein Jahr später | Ein Termin im Kalender |
| Caddy, Telefon widerrufen | Kommt hinein, solange es nicht von der Erlaubnisliste ist | Die Liste pflegen, oder nginx nehmen |
| Caddy, Telefon neu ausgestellt | Ausgesperrt, bis die Liste aktualisiert ist | Die Liste bei jedem Zertifikat aktualisieren |
| Leitung zu Hause hinter Carrier-Grade NAT, kein IPv6 | Nichts erreicht die Portweiterleitung | IPv6, oder die Cloudflare-Variante |
| Eine Flut auf die Leitung zu Hause | Nimmt das Haus vom Netz, nicht nur Home Assistant | Die Cloudflare-Variante, wenn dich das mehr sorgt als ein Dritter |
| iPhone, Wear OS | Wie in der Cloudflare-Variante: Android ist die dokumentierte App, Wear OS will die PKCS12-Datei bei der Einrichtung5 | Testen, bevor du dich darauf verlässt |
Nichts davon ist versteckt. Jedes davon ist eine Aufgabe. Schreib sie mit einem Datum oder einem Befehl daneben auf, und sie bleibt eine Aufgabe und wird keine Überraschung.
Das Ganze selbst besitzen
Die Cloudflare-Variante und diese hier halten denselben Fremden draußen. Der Unterschied ist, wem du vertrauen musst, während sie das tun.
Mit Cloudflare vertraust du einer Firma, dass sie das Tor hält, den Verkehr entschlüsselt und es weiter kostenlos anbietet. Hiermit vertraust du dir selbst, dass du eine CRL auffrischst, eine Liste in Ordnung hältst und merkst, wenn ein Datum heranrückt. Keins von beiden kommt ohne Vertrauen aus. Das eine verteilt es an jemanden, dem du nichts zahlst und den du nicht anrufen kannst. Das andere behält es im Haus, wo du siehst, wenn es scheitert, und es reparieren kannst.
Für die Haustür eines Hauses weiß ich, was mir lieber ist. Die Arbeit sind ein paar Dateien und ein Timer. Das Einzige, was es wirklich verlangt, ist also, dass du dich darum kümmerst, und das hieß es schon immer, etwas zu besitzen.
Cloudflare — DDoS Protection — automatische Erkennung und Abwehr von DDoS, „Available on all plans“. ↩︎
Caddy — tls directive, client_auth —
require_and_verify: „Require clients to present a valid certificate that is verified“; „The one verifier, currently, shipped in standard Caddy isleaf“, der prüft, ob das Zertifikat „is one of a defined set of permitted certificates“. ↩︎ ↩︎nginx — ngx_http_ssl_module —
ssl_verify_depthsteht standardmäßig auf 1;ssl_crl: „When using intermediate certificates, their CRLs should be specified in the same file.“ ↩︎Home Assistant — HTTP integration — Reverse Proxys, vertrauenswürdige Proxys und IP-Sperren; Einstellungen in der Oberfläche seit 2026.8. ↩︎
Home Assistant Companion — Getting Started, TLS client authentication — installieren als „VPN & app user certificate“; Wear OS braucht das Zertifikat als PKCS12 bei der Einrichtung. ↩︎ ↩︎
home-assistant/android — network_security_config.xml — die Vertrauensanker sind
systemplususer: „Additionally trust user added CAs“. ↩︎Home Assistant Companion — Connection security level — Most secure benutzt eine unverschlüsselte URL nur im Heimnetz. ↩︎