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

Le mTLS à la maison avec nginx ou CaddyTéléphonea un certificatTous les autrespas de certificatUne inondationsans certificatRéseau de la maisonRouteurport 443pas de bouclieranti-DDoSnginx ou Caddyremonte à votre AC ?non révoqué ?IPv4 et IPv6step-caHome Assistantapprouve le proxyRefuséà la poignée de mainpasseéchec
Tout arrive maintenant sur votre routeur. Le proxy refuse tout ce qui n’a pas de bon certificat avant que Home Assistant ne le voie. Une inondation n’a besoin d’aucun certificat, et sans rien en amont pour l’absorber, elle atterrit sur votre ligne.
Version Cloudflarenginx ou Caddy à la maison
Qui vérifie le certificatLa périphérie de Cloudflare, avec une règle WAFVotre proxy, pendant la poignée de main TLS
Autorité de certificationGérée par CloudflareLa vôtre, step-ca pour les certificats clients
Qui voit le trafic déchiffréCloudflare, puis Home AssistantVotre proxy et Home Assistant, personne d’autre
Entrée depuis l’extérieurUn tunnel, sortant uniquementUne redirection de port vers le proxy
Un DDoS atterrit surCloudflareVotre ligne domestique
Ce que coûte la connexion d’un inconnuRien à la maisonUne poignée de main TLS sur le proxy, jamais une connexion à Home Assistant
IPv4 et IPv6Les deux, Cloudflare répond sur l’un ou l’autreLes deux si la ligne a les deux et que le proxy écoute sur les deux
RévocationIntégrée, vérifiée par la règlenginx vérifie une CRL ; Caddy tient une liste d’autorisation
Quand le fournisseur est en pannePas d’accès à distanceRien ne change
Coût de fonctionnementOffre gratuiteGratuit, 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.

La révocation avec nginx et avec Caddynginx : une CRL, récupérée par une minuteriestep-carévoque, nouvelle CRL/1.0/crlHTTP simpleMinuterierécupère, rechargenginxvérifie la chaîneCaddy : une liste d'autorisation, tenue à la mainVousà chaque réémissionListecertificats exactsRechargeraprès chaque modifCaddyvérificateur leaf
nginx reçoit une CRL d’une minuterie et la vérifie à chaque connexion. Caddy ne prend pas en charge les CRL, donc la révocation est une liste de certificats exacts que vous tenez à la main.
nginx 1.30.5Caddy 2.11.7
Pas de certificatPoignée de main terminée, 400 No required SSL certificate was sentPoignée de main refusée, curl sortie 55
Certificat révoquéRefusé, 400 The SSL certificate errorLaissé entrer tout droit avec une simple vérification de l’AC
Méthode de révocationUne CRL, vérifiée sur toute la chaîneUne liste d’autorisation de certificats, le vérificateur leaf
WebSocket pour l’applicationExige de poser les en-têtes Upgrade et ConnectionMarche sans configuration supplémentaire
IPv6Seulement en ajoutant listen [::]:443Écoute sur les deux par défaut
ConfigurationUne trentaine de lignesUne 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;
    }
}
LignePourquoi elle est là
listen [::]:443 sslSans 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 onPas de certificat, pas d’entrée. optional laisserait passer la requête pour la juger plus tard
ssl_verify_depth 2step-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_certificateL’intermédiaire et la racine dans un seul fichier
ssl_crlLa 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 ConnectionL’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é :

ClientIPv4IPv6
Pas de certificat400 No required SSL certificate was sent400
phone1200, X-Forwarded-For: 127.0.0.1200, X-Forwarded-For: ::1
phone2, révoqué400 The SSL certificate error400
phone1, montée en WebSocket200, Upgrade: websocket transmis200, 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.

ClientIPv4IPv6
Pas de certificatPoignée de main refuséePoignée de main refusée
phone1, sur la liste200, X-Forwarded-For: 127.0.0.1200, X-Forwarded-For: ::1
phone2, révoqué, vérification de l’AC seule200
phone2, absent de la listePoignée de main refuséePoignée de main refusée
phone1, montée en WebSocket sur HTTP/1.1200, 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églageValeurPourquoi
Trust X-Forwarded-ForActivéSans lui, Home Assistant refuse les requêtes relayées
Trusted proxiesL’adresse du proxy seulement, IPv4 et IPv6, par exemple 192.0.2.20 et 2001:db8::20Seule cette machine peut prétendre relayer pour quelqu’un
Enable IP banningActivéLes bannissements tombent sur la vraie adresse du client que transmet le proxy
Home Assistant URL, Internethttps://ha.example.comL’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 racineCertificat d’AC
Certificat intermédiaireCertificat d’AC
Le fichier PKCS12 du téléphoneCertificat 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

CasCe qui se passeQue faire
La CRL expirenginx refuse tous les téléphonesLa rafraîchir sur minuterie, comme dans l’article step-ca
La CRL racine expirePareil, un an plus tardUne date dans l’agenda
Caddy, téléphone révoquéEntre, sauf s’il est retiré de la liste d’autorisationTenir la liste, ou utiliser nginx
Caddy, téléphone réémisBloqué jusqu’à la mise à jour de la listeMettre la liste à jour à chaque certificat
Ligne domestique derrière un CGNAT, sans IPv6Rien ne peut atteindre la redirection de portIPv6, ou la version Cloudflare
Une inondation visant la ligne domestiqueSort toute la maison d’internet, pas seulement Home AssistantLa version Cloudflare, si ça vous inquiète plus qu’un tiers
iPhone, Wear OSComme dans la version Cloudflare : Android est l’application documentée, Wear OS veut le PKCS12 à la configuration5Tester 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.


  1. Cloudflare — DDoS Protection — détection et atténuation automatiques des DDoS, « Available on all plans ». ↩︎

  2. 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 is leaf », qui vérifie que le certificat « is one of a defined set of permitted certificates ». ↩︎ ↩︎

  3. nginx — ngx_http_ssl_module — ssl_verify_depth vaut 1 par défaut ; ssl_crl : « When using intermediate certificates, their CRLs should be specified in the same file. » ↩︎

  4. Home Assistant — HTTP integration — reverse proxies, proxies de confiance et bannissement d’IP ; réglages dans l’interface depuis 2026.8. ↩︎

  5. 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. ↩︎ ↩︎

  6. home-assistant/android — network_security_config.xml — les ancres de confiance sont system plus user : « Additionally trust user added CAs ». ↩︎

  7. Home Assistant Companion — Connection security level — Most secure n’utilise une URL non chiffrée que sur le réseau de la maison. ↩︎