Hetzelfde slot, je eigen deur
De Home Assistant-app via Cloudflare, vergrendeld met een certificaat zet het slot aan de rand van Cloudflare. De telefoon heeft een certificaat, Cloudflare controleert het, en alles zonder certificaat bereikt het huis nooit. Het werkt, het is gratis, en het betekent dat Cloudflare de poort bewaakt en het verkeer leest terwijl het passeert.
Dit is hetzelfde slot met de deur in je eigen huis. nginx of Caddy controleert het certificaat. Je eigen certificaatautoriteit geeft het uit. Niemand anders zit in het pad. De aanmeldpagina blijft onzichtbaar voor alles zonder certificaat, dus wat er verandert is niet of vreemden binnenkomen. Het is wie het werk doet, en wie het risico draagt als het misgaat.
Alles hier is gebouwd en getest: nginx 1.30.5 en Caddy 2.11.7 in Podman, certificaten van step-ca 0.30.2, elke proxy aangesproken zonder certificaat, met een goed certificaat, met een ingetrokken certificaat en met een WebSocket-upgrade, over IPv4 en IPv6. De backend in de test was een kleine invaller die de ontvangen headers terugstuurt, dus de resultaten laten precies zien wat Home Assistant zou krijgen.
Wat er verandert als Cloudflare wegvalt
| Cloudflare-versie | nginx of Caddy thuis | |
|---|---|---|
| Wie controleert het certificaat | De rand van Cloudflare, met een WAF-regel | Je proxy, tijdens de TLS-handshake |
| Certificaatautoriteit | Beheerd door Cloudflare | Je eigen, step-ca voor clientcertificaten |
| Wie ziet het verkeer ontsleuteld | Cloudflare, dan Home Assistant | Je proxy en Home Assistant, verder niemand |
| Toegang van buitenaf | Een tunnel, alleen uitgaand | Een port forward naar de proxy |
| Een DDoS landt op | Cloudflare | Je thuislijn |
| De verbinding van een vreemde kost | Thuis niets | Een TLS-handshake op de proxy, nooit een aanmelding bij Home Assistant |
| IPv4 en IPv6 | Beide, Cloudflare antwoordt op allebei | Beide als de lijn beide heeft en de proxy op beide luistert |
| Intrekking | Ingebouwd, gecontroleerd door de regel | nginx controleert een CRL; Caddy houdt een toegestane lijst bij |
| Als de aanbieder plat ligt | Geen toegang op afstand | Er verandert niets |
| Lopende kosten | Gratis abonnement | Gratis, plus je tijd aan de CA |
De twee rijen die het meest tellen trekken elk een andere kant op. Niemand anders leest je verkeer of kan je uitzetten, en dat is soevereiniteit, de hele reden om het te doen. Maar de port forward is terug, dus scans en floods raken jouw lijn in plaats van die van Cloudflare. Het verschil met de gewone port forward in de vergelijking van het Cloudflare-bericht is wat ze vinden als ze aankomen: een proxy die de verbinding tijdens de handshake laat vallen, niet de aanmeldpagina van Home Assistant.
Wees duidelijk over wat dat wel en niet oplevert. Je eigen reverse proxy geeft je helemaal geen DDoS-bescherming. mTLS houdt een vreemde buiten. Het doet niets om te voorkomen dat een vreemde je lijn vol trekt, en een flood heeft geen certificaat nodig om dat te doen. Cloudflare vangt dat voor je op bij elk abonnement, het gratis abonnement inbegrepen1. Hier zit er stroomopwaarts niemand om het op te vangen, dus een flood die groot genoeg is haalt het hele huis van het internet, en de proxy kan alleen verbindingen weigeren die hij te zien krijgt.
nginx of Caddy
Beide doen het werk. Ze verschillen op één plek die ertoe doet. Intrekking.
| nginx 1.30.5 | Caddy 2.11.7 | |
|---|---|---|
| Geen certificaat | Handshake voltooid, 400 No required SSL certificate was sent | Handshake geweigerd, curl exit 55 |
| Ingetrokken certificaat | Geweigerd, 400 The SSL certificate error | Gewoon binnengelaten met alleen een CA-controle |
| Intrekkingsmethode | Een CRL, gecontroleerd over de hele keten | Een toegestane lijst van certificaten, de verifier leaf |
| WebSocket voor de app | Headers Upgrade en Connection moeten gezet worden | Werkt zonder extra config |
| IPv6 | Alleen met listen [::]:443 erbij | Luistert standaard op beide |
| Config | Ongeveer 30 regels | Ongeveer 15 regels |
Intrekking is degene om op te beslissen. De documentatie van Caddy noemt precies één verifier die met standaard-Caddy wordt meegeleverd, leaf, en die controleert of het certificaat “is one of a defined set of permitted certificates”2. Er is geen CRL en geen OCSP voor clientcertificaten. Wijs Caddy alleen naar de CA en een ingetrokken telefoon loopt zo naar binnen. De test liet precies dat zien: phone2, een uur eerder ingetrokken, kreeg een 200. De toegestane lijst dicht het gat. Het is ook een lijst die je met de hand bijhoudt, zolang je het draait.
De nginx-config
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;
}
}
| Regel | Waarom hij er staat |
|---|---|
listen [::]:443 ssl | Zonder deze regel is nginx alleen IPv4, en een telefoon op een IPv6-netwerk kan nergens landen |
ssl_verify_client on | Geen certificaat, geen toegang. optional zou het verzoek doorlaten om later te worden beoordeeld |
ssl_verify_depth 2 | step-ca ondertekent vanaf een intermediate, dus de keten is telefoon, intermediate, root. De standaard van 1 houdt te vroeg op3 |
ssl_client_certificate | De intermediate en de root in één bestand |
ssl_crl | De CRL van step-ca en een root-CRL in één bestand. nginx controleert elke schakel in de keten, dus hij heeft beide nodig |
Upgrade en Connection | De app praat met Home Assistant over een WebSocket. Zonder deze headers laadt de app en blijft hij daarna hangen |
Het CRL-bestand is het deel dat zorg vraagt, en alles daarover staat in Clientcertificaten van step-ca: waarom de CRL van step-ca wordt geweigerd tot een sjabloon de scope repareert, waarom nginx ook een CRL van de root wil, en de timer die hem ververst voordat hij verloopt. Sla dat over en nginx sluit elke telefoon buiten, de goede inbegrepen.
Wat de test terugkreeg:
| Client | IPv4 | IPv6 |
|---|---|---|
| Geen certificaat | 400 No required SSL certificate was sent | 400 |
| phone1 | 200, X-Forwarded-For: 127.0.0.1 | 200, X-Forwarded-For: ::1 |
| phone2, ingetrokken | 400 The SSL certificate error | 400 |
| phone1, WebSocket-upgrade | 200, Upgrade: websocket doorgegeven | 200, Upgrade: websocket doorgegeven |
Voordat listen [::]:8443 erin stond, maakte het IPv6-verzoek helemaal geen verbinding. curl exit 7.
De Caddy-config
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 betekent dat het certificaat aanwezig moet zijn en naar de CA moet ketenen2. verifier leaf is de intrekking. Alleen de certificaten in allowed-phones.pem komen binnen, dus een telefoon intrekken betekent zijn certificaat uit dat bestand halen en herladen.
| Client | IPv4 | IPv6 |
|---|---|---|
| Geen certificaat | Handshake geweigerd | Handshake geweigerd |
| phone1, op de lijst | 200, X-Forwarded-For: 127.0.0.1 | 200, X-Forwarded-For: ::1 |
| phone2, ingetrokken, alleen CA-controle | 200 | |
| phone2, niet op de lijst | Handshake geweigerd | Handshake geweigerd |
| phone1, WebSocket-upgrade over HTTP/1.1 | 200, Upgrade: websocket doorgegeven |
De toegestane lijst heeft een eigen valkuil, en de test liep er zo in. De lijst bevat exacte certificaten, geen namen. Geef het certificaat van een telefoon opnieuw uit en het nieuwe staat niet op de lijst. phone1 werd halverwege de tests opnieuw uitgegeven en Caddy weigerde hem met tls alert bad certificate tot de lijst opnieuw was opgebouwd. Elke vernieuwing is nu twee klussen. Geef het certificaat uit, en werk dan de lijst bij. Vergeet de tweede en de telefoon zit voor niks buitengesloten.
Caddy logt bij het starten ook enabling strict SNI-Host enforcement because TLS client auth is configured. Dat is hem die het juiste doet: een verzoek moet in de handshake om dezelfde hostnaam vragen als in het HTTP-verzoek, zodat niemand de certificaatcontrole voor de ene site kan halen en dan om een andere kan vragen.
De kant van Home Assistant
Dezelfde instellingen als bij de Cloudflare-versie, gericht op een andere proxy. Onder Settings → System → Network → HTTP server4:
| Instelling | Waarde | Waarom |
|---|---|---|
| Trust X-Forwarded-For | Aan | Zonder deze instelling weigert Home Assistant verzoeken via een proxy |
| Trusted proxies | Alleen het adres van de proxy, IPv4 en IPv6, bijvoorbeeld 192.0.2.20 en 2001:db8::20 | Alleen die machine mag beweren dat hij voor iemand doorstuurt |
| Enable IP banning | Aan | Bans komen terecht op het echte clientadres dat de proxy doorgeeft |
| Home Assistant URL, Internet | https://ha.example.com | De app neemt dit over als zijn externe URL |
Zet het IPv6-adres van de proxy er ook in, naast het IPv4-adres. De test laat zien dat nginx over IPv6 ::1 doorgeeft als clientadres, en in gebruik geeft hij door waar de telefoon ook vandaan kwam. Als Home Assistant alleen het IPv4-adres van de proxy vertrouwt, wordt elk verzoek dat er via de proxy over IPv6 aankomt geweigerd.
De telefoon
Van step-ca heeft de telefoon drie bestanden nodig. Alles daarover staat in het bericht over clientcertificaten van step-ca, en het inrichten van de CA zelf in Je eigen certificaatautoriteit.
| Bestand | Installeren als |
|---|---|
| Rootcertificaat | CA-certificaat |
| Intermediate-certificaat | CA-certificaat |
| Het PKCS12-bestand van de telefoon | Gebruikerscertificaat voor VPN en apps5 |
Het servercertificaat op de proxy komt ook van step-ca. De Android-app van Home Assistant vertrouwt CA-certificaten die de gebruiker installeert6, dus hij accepteert het, en dat is een tweede slot, geen last. Een apparaat zonder jouw root vertrouwt de server niet, nog voordat de vraag naar zijn eigen certificaat aan de orde komt.
Zet daarna het verbindingsbeveiligingsniveau van de app op Most secure, net als bij de Cloudflare-versie7.
Waar het tekortschiet
| Geval | Wat er gebeurt | Wat te doen |
|---|---|---|
| CRL verloopt | nginx weigert elke telefoon | Ververs hem op een timer, zoals in het step-ca-bericht |
| Root-CRL verloopt | Hetzelfde, een jaar later | Een datum in de agenda |
| Caddy, telefoon ingetrokken | Komt binnen tenzij hij van de toegestane lijst af is | Houd de lijst bij, of gebruik nginx |
| Caddy, telefoon opnieuw uitgegeven | Buitengesloten tot de lijst is bijgewerkt | Werk de lijst bij bij elk certificaat |
| Thuislijn achter carrier-grade NAT, geen IPv6 | Niets kan de port forward bereiken | IPv6, of de Cloudflare-versie |
| Een flood gericht op de thuislijn | Haalt het hele huis offline, niet alleen Home Assistant | De Cloudflare-versie, als je je daar meer zorgen om maakt dan om een derde partij |
| iPhone, Wear OS | Hetzelfde als bij de Cloudflare-versie: Android is de gedocumenteerde app, Wear OS wil de PKCS12 bij de installatie5 | Test het voordat je erop vertrouwt |
Geen van deze is verborgen. Elk is een klus. Schrijf het op met een datum of een commando ernaast en het blijft een klus, geen verrassing.
Het hele ding zelf bezitten
De Cloudflare-versie en deze houden dezelfde vreemde buiten. Het verschil is wie je moet vertrouwen terwijl ze dat doen.
Met Cloudflare vertrouw je een bedrijf om de poort te bewaken, het verkeer te ontsleutelen en het gratis te blijven aanbieden. Hiermee vertrouw je jezelf om een CRL te verversen, een lijst op orde te houden en op te merken wanneer een datum langskomt. Geen van beide is vrij van vertrouwen. De een legt het bij iemand die je niets betaalt en niet kunt bellen. De ander houdt het in huis, waar je het kunt zien falen en kunt repareren.
Voor de voordeur van een huis weet ik wat ik liever heb. Het werk is een paar bestanden en een timer. Als zodanig vraagt het eigenlijk alleen dat je ervoor zorgt, en dat is wat iets bezitten altijd heeft betekend.
Cloudflare — DDoS Protection — automatische detectie en mitigatie van 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”, die controleert of het certificaat “is one of a defined set of permitted certificates”. ↩︎ ↩︎nginx — ngx_http_ssl_module —
ssl_verify_depthstaat standaard op 1;ssl_crl: “When using intermediate certificates, their CRLs should be specified in the same file.” ↩︎Home Assistant — HTTP integration — reverse proxy’s, vertrouwde proxy’s en IP-bans; instellingen in de interface sinds 2026.8. ↩︎
Home Assistant Companion — Getting Started, TLS client authentication — installeren als “VPN & app user certificate”; Wear OS heeft het certificaat als PKCS12 nodig tijdens de onboarding. ↩︎ ↩︎
home-assistant/android — network_security_config.xml — vertrouwensankers zijn
systemplususer: “Additionally trust user added CAs”. ↩︎Home Assistant Companion — Connection security level — Most secure gebruikt alleen op het thuisnetwerk een onversleutelde URL. ↩︎