Configuration du reverse proxy
Configurez Traefik, Nginx ou Caddy devant le Hub Watchflare. Couvre le reverse proxy HTTP pour le tableau de bord et le passthrough TCP pour le port gRPC.
Le Hub expose deux ports, avec des exigences de proxy différentes :
| Port | Protocole | Type de proxy |
|---|---|---|
8080 | HTTP | Reverse proxy standard, terminaison TLS ici |
50051 | gRPC / TLS 1.3 | Passthrough TCP, sans terminaison TLS |
Attention
Le port gRPC doit être relayé au niveau TCP, sans terminaison TLS. Les agents épinglent le certificat CA du Hub à l’enrôlement. Si le proxy présente un certificat différent, chaque agent refusera de se connecter.
Traefik
Testé : ✅ Traefik v3
Traefik a besoin de deux routes : un reverse proxy HTTP pour le tableau de bord, et un passthrough TCP pour le gRPC.
Config statique
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
grpc:
address: ":50051"
certificatesResolvers:
letsencrypt:
acme:
email: you@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web Redémarrez Traefik après avoir modifié la config statique. Les changements de config dynamique sont rechargés à chaud.
Config dynamique
http:
routers:
watchflare-http:
entryPoints: [web]
rule: "Host(`watchflare.example.com`)"
middlewares: [redirect-https]
service: watchflare-http
watchflare-https:
entryPoints: [websecure]
rule: "Host(`watchflare.example.com`)"
tls:
certResolver: letsencrypt
service: watchflare-http
middlewares:
redirect-https:
redirectScheme:
scheme: https
permanent: true
services:
watchflare-http:
loadBalancer:
servers:
- url: "http://HUB_IP:8080"
tcp:
routers:
watchflare-grpc:
entryPoints: [grpc]
rule: "HostSNI(`*`)"
tls:
passthrough: true
service: watchflare-grpc
services:
watchflare-grpc:
loadBalancer:
servers:
- address: "HUB_IP:50051" Remplacez HUB_IP par l’IP ou le nom d’hôte du serveur qui exécute le Hub, et watchflare.example.com par votre domaine.
Labels Docker Compose
Si Traefik et le Hub s’exécutent dans la même stack Compose :
services:
traefik:
image: traefik:v3
ports:
- "80:80"
- "443:443"
- "50051:50051"
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.grpc.address=:50051"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
watchflare:
labels:
- "traefik.enable=true"
# HTTP → HTTPS
- "traefik.http.routers.watchflare.rule=Host(`watchflare.example.com`)"
- "traefik.http.routers.watchflare.entrypoints=websecure"
- "traefik.http.routers.watchflare.tls.certresolver=letsencrypt"
- "traefik.http.services.watchflare.loadbalancer.server.port=8080"
# gRPC TCP passthrough
- "traefik.tcp.routers.watchflare-grpc.entrypoints=grpc"
- "traefik.tcp.routers.watchflare-grpc.rule=HostSNI(`*`)"
- "traefik.tcp.routers.watchflare-grpc.tls.passthrough=true"
- "traefik.tcp.services.watchflare-grpc.loadbalancer.server.port=50051" Nginx
Testé : ⚠️ Non testé, config d’après la documentation Nginx
Nginx exige le module stream pour le passthrough TCP sur le port gRPC (--with-stream à la compilation, inclus dans la plupart des distributions Linux).
# HTTPS reverse proxy (dashboard)
server {
listen 443 ssl;
server_name watchflare.example.com;
ssl_certificate /etc/letsencrypt/live/watchflare.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/watchflare.example.com/privkey.pem;
location / {
proxy_pass http://HUB_IP:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for SSE (real-time dashboard updates)
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
}
}
server {
listen 80;
server_name watchflare.example.com;
return 301 https://$host$request_uri;
}
# TCP passthrough (gRPC, do not terminate TLS)
stream {
server {
listen 50051;
proxy_pass HUB_IP:50051;
}
} Le bloc stream {} doit être au niveau supérieur de nginx.conf, pas à l’intérieur d’un bloc http {}.
Attention
proxy_http_version 1.1, proxy_set_header Connection "" et proxy_buffering off sont tous obligatoires dans le bloc HTTP. Sans eux, le flux SSE qui alimente les mises à jour en temps réel du statut des hôtes et des métriques sera mis en tampon et le tableau de bord ne se mettra pas à jour en direct.
Caddy
Testé : ⚠️ Non testé, config d’après la documentation Caddy
Caddy gère le HTTPS et le renouvellement des certificats automatiquement. Pour le passthrough TCP du gRPC, le plugin caddy-l4 est obligatoire.
HTTP (tableau de bord). Caddyfile standard, aucun plugin n’est nécessaire :
watchflare.example.com {
reverse_proxy HUB_IP:8080
} Passthrough TCP gRPC. Exige caddy-l4. Ajoutez un bloc layer4 dans une config JSON ou via une compilation xcaddy avec le plugin. Consultez la documentation caddy-l4 pour la syntaxe.
Pourquoi HostSNI("*")
Pendant le handshake TLS, l’agent envoie un SNI égal à server_name dans agent.conf, qui vaut par défaut watchflare (le CN du certificat généré par le Hub). Une règle du type HostSNI("watchflare") devrait correspondre exactement à cette valeur.
Utiliser HostSNI("*") évite les décalages si le CN du certificat change (par ex. lors du passage à TLS_MODE=custom avec un CN différent). C’est sûr, car le port 50051 est dédié au gRPC Watchflare. La sécurité est assurée par TLS 1.3 et l’authentification HMAC par requête, pas par le filtrage SNI.
Limitation de débit
Le tableau de bord du Hub est une application monopage : un chargement à froid récupère des dizaines de fichiers statiques depuis /_app/immutable/* en parallèle, davantage sur les pages plus lourdes. Si votre proxy impose une limite de débit par requête, le burst doit être assez élevé pour absorber un chargement de page complet. Un burst trop bas renvoie 429 pour une partie de ces fichiers, ce que le navigateur signale comme NS_ERROR_CORRUPTED_CONTENT et un chargement de page en échec (le tableau de bord affiche alors une page « Internal Error »).
Attention
Limiter le débit des fichiers statiques immuables au nombre de requêtes apporte rarement de la valeur et casse facilement le tableau de bord. Utilisez un burst généreux (quelques centaines), ou bornez la limite de débit aux chemins d’API uniquement.
Les valeurs ci-dessous ne sont que des exemples. Dimensionnez le burst selon le poids de vos pages et le nombre de clients. Il n’y a pas de nombre imposé.
Pour Traefik, il s’agit de la valeur burst du middleware rateLimit :
http:
middlewares:
watchflare-ratelimit:
rateLimit:
average: 300 # exemple, à ajuster
burst: 300 # exemple, à ajuster Pour Nginx, utilisez limit_req avec un burst assez grand pour un chargement de page complet (ajoutez nodelay pour que le burst soit servi immédiatement, sans mise en file) :
# http {} context
limit_req_zone $binary_remote_addr zone=watchflare:10m rate=300r/s; # example
# inside the location block
location / {
limit_req zone=watchflare burst=300 nodelay; # exemple, à ajuster
proxy_pass http://HUB_IP:8080;
# ... your other proxy_set_header / SSE settings
} Caddy n’a pas de limitation de débit intégrée, il n’y a donc rien à ajuster par défaut. Si vous ajoutez une limitation de débit via un plugin, dimensionnez-la généreusement ou bornez-la aux chemins d’API uniquement, en suivant le même principe.
Après la mise en place
Une fois le HTTPS fonctionnel, mettez à jour votre .env pour que les cookies de session soient correctement marqués Secure :
COOKIE_DOMAIN=watchflare.example.com Voir configuration HTTPS pour le détail de la sécurité des cookies et comment vérifier que cela fonctionne.