Le même verrou, votre propre porte
L’application Home Assistant via Cloudflare, verrouillée par un certificat place le verrou à la périphérie de Cloudflare. Le téléphone détient un certificat, Cloudflare le vérifie, et rien de ce qui n’en a pas n’atteint la maison. Ça marche, c’est gratuit, et ça veut dire que Cloudflare tient la porte et lit le trafic au passage.
Ici, c’est le même verrou avec la porte dans votre propre maison. nginx ou Caddy vérifie le certificat. Votre propre autorité de certification l’émet. Personne d’autre n’est sur le chemin. La page de connexion reste invisible pour tout ce qui n’a pas de certificat, donc ce qui change, ce n’est pas si des inconnus entrent. C’est qui fait le travail, et qui porte le risque quand ça tourne mal.
Tout ce qui suit a été construit et testé : nginx 1.30.5 et Caddy 2.11.7 sous Podman, des certificats de step-ca 0.30.2, chaque proxy attaqué sans certificat, avec un bon certificat, avec un révoqué et avec une montée en WebSocket, en IPv4 et en IPv6. Le backend du test était un petit remplaçant qui renvoie les en-têtes qu’il reçoit, donc les résultats montrent exactement ce que Home Assistant recevrait.
Ce qui change quand Cloudflare s’en va
| Version Cloudflare | nginx ou Caddy à la maison | |
|---|---|---|
| Qui vérifie le certificat | La périphérie de Cloudflare, avec une règle WAF | Votre proxy, pendant la poignée de main TLS |
| Autorité de certification | Gérée par Cloudflare | La vôtre, step-ca pour les certificats clients |
| Qui voit le trafic déchiffré | Cloudflare, puis Home Assistant | Votre proxy et Home Assistant, personne d’autre |
| Entrée depuis l’extérieur | Un tunnel, sortant uniquement | Une redirection de port vers le proxy |
| Un DDoS atterrit sur | Cloudflare | Votre ligne domestique |
| Ce que coûte la connexion d’un inconnu | Rien à la maison | Une poignée de main TLS sur le proxy, jamais une connexion à Home Assistant |
| IPv4 et IPv6 | Les deux, Cloudflare répond sur l’un ou l’autre | Les deux si la ligne a les deux et que le proxy écoute sur les deux |
| Révocation | Intégrée, vérifiée par la règle | nginx vérifie une CRL ; Caddy tient une liste d’autorisation |
| Quand le fournisseur est en panne | Pas d’accès à distance | Rien ne change |
| Coût de fonctionnement | Offre gratuite | Gratuit, plus votre temps sur l’AC |
Les deux lignes qui comptent le plus tirent en sens opposés. Personne d’autre ne lit votre trafic ni ne peut vous couper, et c’est la souveraineté, toute la raison de le faire. Mais la redirection de port est de retour, donc les scans et les inondations frappent votre ligne au lieu de celle de Cloudflare. La différence avec la simple redirection de port du comparatif de l’article Cloudflare, c’est ce qu’ils trouvent en arrivant : un proxy qui coupe la connexion pendant la poignée de main, pas la page de connexion de Home Assistant.
Soyez clair sur ce que ça achète et ce que ça n’achète pas. Votre propre reverse proxy ne vous donne aucune protection DDoS. Le mTLS empêche un inconnu d’entrer. Il ne fait rien pour empêcher un inconnu de saturer votre ligne, et une inondation n’a pas besoin de certificat pour la saturer. Cloudflare absorbe ça pour vous sur toutes ses offres, la gratuite comprise1. Ici, il n’y a personne en amont pour l’absorber, donc une inondation assez grosse sort toute la maison d’internet, et le proxy ne peut refuser que les connexions qu’il arrive à voir.
nginx ou Caddy
Les deux font le travail. Ils diffèrent à un endroit qui compte. La révocation.
| nginx 1.30.5 | Caddy 2.11.7 | |
|---|---|---|
| Pas de certificat | Poignée de main terminée, 400 No required SSL certificate was sent | Poignée de main refusée, curl sortie 55 |
| Certificat révoqué | Refusé, 400 The SSL certificate error | Laissé entrer tout droit avec une simple vérification de l’AC |
| Méthode de révocation | Une CRL, vérifiée sur toute la chaîne | Une liste d’autorisation de certificats, le vérificateur leaf |
| WebSocket pour l’application | Exige de poser les en-têtes Upgrade et Connection | Marche sans configuration supplémentaire |
| IPv6 | Seulement en ajoutant listen [::]:443 | Écoute sur les deux par défaut |
| Configuration | Une trentaine de lignes | Une quinzaine de lignes |
C’est sur la révocation qu’il faut trancher. La documentation de Caddy liste exactement un vérificateur livré avec Caddy standard, leaf, et il vérifie si le certificat « is one of a defined set of permitted certificates »2. Il n’y a ni CRL ni OCSP pour les certificats clients. Pointez Caddy sur l’AC seule et un téléphone révoqué entre comme chez lui. Le test a montré exactement ça : phone2, révoqué une heure plus tôt, a eu un 200. La liste d’autorisation comble le trou. C’est aussi une liste que vous tenez à la main, aussi longtemps que vous faites tourner tout ça.
La configuration nginx
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;
}
}
| Ligne | Pourquoi elle est là |
|---|---|
listen [::]:443 ssl | Sans elle, nginx ne fait que de l’IPv4, et un téléphone sur un réseau IPv6 n’a nulle part où atterrir |
ssl_verify_client on | Pas de certificat, pas d’entrée. optional laisserait passer la requête pour la juger plus tard |
ssl_verify_depth 2 | step-ca signe depuis un intermédiaire, donc la chaîne est téléphone, intermédiaire, racine. Le défaut de 1 s’arrête avant3 |
ssl_client_certificate | L’intermédiaire et la racine dans un seul fichier |
ssl_crl | La CRL de step-ca et une CRL racine dans un seul fichier. nginx vérifie chaque maillon de la chaîne, donc il lui faut les deux |
Upgrade et Connection | L’application parle à Home Assistant par WebSocket. Sans eux, l’application se charge puis reste plantée là |
Le fichier CRL est la partie qui demande du soin, et tout est dans Des certificats clients depuis step-ca : pourquoi la CRL de step-ca est rejetée tant qu’un modèle ne corrige pas sa portée, pourquoi nginx veut aussi une CRL de la racine, et la minuterie qui la rafraîchit avant qu’elle n’expire. Sautez ça et nginx bloque tous les téléphones, les bons compris.
Ce que le test a renvoyé :
| Client | IPv4 | IPv6 |
|---|---|---|
| Pas de certificat | 400 No required SSL certificate was sent | 400 |
| phone1 | 200, X-Forwarded-For: 127.0.0.1 | 200, X-Forwarded-For: ::1 |
| phone2, révoqué | 400 The SSL certificate error | 400 |
| phone1, montée en WebSocket | 200, Upgrade: websocket transmis | 200, Upgrade: websocket transmis |
Avant l’ajout de listen [::]:8443, la requête IPv6 ne se connectait pas du tout. curl sortie 7.
La configuration Caddy
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 veut dire que le certificat doit être présent et doit remonter jusqu’à l’AC2. verifier leaf, c’est la révocation. Seuls les certificats de allowed-phones.pem entrent, donc révoquer un téléphone veut dire retirer son certificat de ce fichier et recharger.
| Client | IPv4 | IPv6 |
|---|---|---|
| Pas de certificat | Poignée de main refusée | Poignée de main refusée |
| phone1, sur la liste | 200, X-Forwarded-For: 127.0.0.1 | 200, X-Forwarded-For: ::1 |
| phone2, révoqué, vérification de l’AC seule | 200 | |
| phone2, absent de la liste | Poignée de main refusée | Poignée de main refusée |
| phone1, montée en WebSocket sur HTTP/1.1 | 200, Upgrade: websocket transmis |
La liste d’autorisation a son propre piège, et le test est tombé en plein dedans. La liste contient des certificats exacts, pas des noms. Réémettez le certificat d’un téléphone et le nouveau n’est pas sur la liste. phone1 a été réémis à mi-parcours du test et Caddy l’a refusé avec tls alert bad certificate jusqu’à ce que la liste soit reconstruite. Chaque renouvellement, ce sont maintenant deux tâches. Émettre le certificat, puis mettre la liste à jour. Oubliez la seconde et le téléphone est bloqué pour rien.
Caddy journalise aussi enabling strict SNI-Host enforcement because TLS client auth is configured au démarrage. C’est lui qui fait ce qu’il faut : une requête doit demander le même nom d’hôte dans la poignée de main que dans la requête HTTP, pour que personne ne puisse passer la vérification de certificat pour un site puis en demander un autre.
Le côté Home Assistant
Les mêmes réglages que dans la version Cloudflare, visant un autre proxy. Sous Settings → System → Network → HTTP server4 :
| Réglage | Valeur | Pourquoi |
|---|---|---|
| Trust X-Forwarded-For | Activé | Sans lui, Home Assistant refuse les requêtes relayées |
| Trusted proxies | L’adresse du proxy seulement, IPv4 et IPv6, par exemple 192.0.2.20 et 2001:db8::20 | Seule cette machine peut prétendre relayer pour quelqu’un |
| Enable IP banning | Activé | Les bannissements tombent sur la vraie adresse du client que transmet le proxy |
| Home Assistant URL, Internet | https://ha.example.com | L’application la reprend comme URL externe |
Listez l’adresse IPv6 du proxy en plus de son adresse IPv4. Le test montre nginx transmettant ::1 comme adresse du client en IPv6, et en service il transmet l’adresse d’où vient le téléphone, quelle qu’elle soit. Si Home Assistant ne fait confiance qu’à l’adresse IPv4 du proxy, chaque requête qui lui arrive du proxy en IPv6 est refusée.
Le téléphone
Depuis step-ca, le téléphone a besoin de trois fichiers. Tout est couvert dans l’article sur les certificats clients de step-ca, et la mise en place de l’AC elle-même dans Votre propre autorité de certification.
| Fichier | À installer comme |
|---|---|
| Certificat racine | Certificat d’AC |
| Certificat intermédiaire | Certificat d’AC |
| Le fichier PKCS12 du téléphone | Certificat utilisateur VPN et applications5 |
Le certificat serveur du proxy vient lui aussi de step-ca. L’application Android de Home Assistant fait confiance aux certificats d’AC installés par l’utilisateur6, donc elle l’accepte, et c’est un second verrou, pas une gêne. Un appareil qui ne détient pas votre racine ne fait pas confiance au serveur, avant même que se pose la question de son propre certificat.
Réglez ensuite le niveau de sécurité de connexion de l’application sur Most secure, comme dans la version Cloudflare7.
Là où ça coince
| Cas | Ce qui se passe | Que faire |
|---|---|---|
| La CRL expire | nginx refuse tous les téléphones | La rafraîchir sur minuterie, comme dans l’article step-ca |
| La CRL racine expire | Pareil, un an plus tard | Une date dans l’agenda |
| Caddy, téléphone révoqué | Entre, sauf s’il est retiré de la liste d’autorisation | Tenir la liste, ou utiliser nginx |
| Caddy, téléphone réémis | Bloqué jusqu’à la mise à jour de la liste | Mettre la liste à jour à chaque certificat |
| Ligne domestique derrière un CGNAT, sans IPv6 | Rien ne peut atteindre la redirection de port | IPv6, ou la version Cloudflare |
| Une inondation visant la ligne domestique | Sort toute la maison d’internet, pas seulement Home Assistant | La version Cloudflare, si ça vous inquiète plus qu’un tiers |
| iPhone, Wear OS | Comme dans la version Cloudflare : Android est l’application documentée, Wear OS veut le PKCS12 à la configuration5 | Tester avant de s’y fier |
Aucun de ces cas n’est caché. Chacun est une tâche. Notez-la avec une date ou une commande à côté et elle reste une tâche, pas une surprise.
Posséder le tout
La version Cloudflare et celle-ci tiennent dehors le même inconnu. La différence, c’est à qui vous devez faire confiance pendant qu’elles le font.
Avec Cloudflare, vous faites confiance à une entreprise pour tenir la porte, déchiffrer le trafic et continuer à l’offrir gratuitement. Avec ceci, vous vous faites confiance pour rafraîchir une CRL, tenir une liste à jour et remarquer quand une date arrive. Aucune des deux n’échappe à la confiance. L’une la confie à quelqu’un que vous ne payez pas et que vous ne pouvez pas appeler. L’autre la garde à la maison, où vous pouvez la voir échouer et la réparer.
Pour la porte d’entrée d’une maison, je sais ce que je préfère. Le travail, c’est quelques fichiers et une minuterie. En tant que tel, la seule chose qu’il demande vraiment, c’est que vous vous en occupiez, ce que posséder quoi que ce soit a toujours voulu dire.
Cloudflare — DDoS Protection — détection et atténuation automatiques des 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», qui vérifie que le certificat « is one of a defined set of permitted certificates ». ↩︎ ↩︎nginx — ngx_http_ssl_module —
ssl_verify_depthvaut 1 par défaut ;ssl_crl: « When using intermediate certificates, their CRLs should be specified in the same file. » ↩︎Home Assistant — HTTP integration — reverse proxies, proxies de confiance et bannissement d’IP ; réglages dans l’interface depuis 2026.8. ↩︎
Home Assistant Companion — Getting Started, TLS client authentication — installer comme « VPN & app user certificate » ; Wear OS a besoin du certificat fourni en PKCS12 pendant la configuration initiale. ↩︎ ↩︎
home-assistant/android — network_security_config.xml — les ancres de confiance sont
systemplususer: « Additionally trust user added CAs ». ↩︎Home Assistant Companion — Connection security level — Most secure n’utilise une URL non chiffrée que sur le réseau de la maison. ↩︎