Configuration du reverse proxy
Placez Traefik, Nginx ou Caddy devant le Hub Watchflare. Reverse proxy HTTP pour le tableau de bord, passthrough TCP pour le port gRPC.
Le Hub ouvre deux ports. Ils ne se proxifient pas de la même façon :
| Port | Protocole | Type de proxy |
|---|---|---|
8080 | HTTP | Reverse proxy classique : c’est ici qu’on termine le TLS |
50051 | gRPC / TLS 1.3 | Passthrough TCP, sans terminer le TLS |
Attention
Le gRPC doit passer en TCP, TLS intact. Les agents figent la CA du Hub à l’enregistrement. Un autre certificat sur le proxy, et plus aucun agent ne se connecte.
Traefik
Testé : ✅ Traefik v3
Deux routes : HTTP pour le tableau de bord, TCP passthrough 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 un changement de config statique. La config dynamique se recharge toute seule.
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 Hub, et watchflare.example.com par votre domaine.
Labels Docker Compose
Si Traefik et le Hub sont dans le même 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 a besoin du module stream pour le passthrough TCP du port gRPC (--with-stream à la compilation, présent dans la plupart des distros 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 à la racine de nginx.conf, pas dans un http {}.
Attention
Dans le bloc HTTP : proxy_http_version 1.1, proxy_set_header Connection "" et proxy_buffering off — les trois. Sans ça, le flux SSE (statut et métriques en direct) reste dans un buffer, et le tableau de bord ne bouge plus.
Caddy
Testé : ⚠️ Non testé, config d’après la documentation Caddy
Caddy gère le HTTPS et le renouvellement des certificats tout seul. Pour le passthrough TCP du gRPC, il faut le plugin caddy-l4.
HTTP (tableau de bord). Caddyfile standard, pas de plugin :
watchflare.example.com {
reverse_proxy HUB_IP:8080
} Passthrough TCP gRPC. Il faut caddy-l4. Ajoutez un bloc layer4 en JSON, ou construisez Caddy avec xcaddy et le plugin. Syntaxe : documentation caddy-l4.
Pourquoi HostSNI("*")
Pendant le handshake TLS, l’agent envoie un SNI égal à server_name dans agent.conf — par défaut watchflare, le CN du certificat généré par le Hub. Une règle HostSNI("watchflare") devrait coller à cette valeur au caractère près.
HostSNI("*") évite les décalages si le CN change (par ex. TLS_MODE=custom avec un autre CN). C’est sûr : le port 50051 ne sert qu’au gRPC Watchflare. La sécu vient du TLS 1.3 et du HMAC par requête, pas du filtrage SNI.
Limitation de débit
Le tableau de bord est une SPA : un chargement à froid tire des dizaines d’assets depuis /_app/immutable/* en parallèle, davantage sur les pages lourdes. Si le proxy limite le débit, le burst doit absorber un chargement complet. Un burst trop bas répond 429 sur une partie des fichiers : le navigateur affiche NS_ERROR_CORRUPTED_CONTENT, la page échoue, le tableau de bord montre « Internal Error ».
Attention
Compter les requêtes sur des assets immutables n’apporte presque rien, et casse facilement le tableau de bord. Burst généreux (quelques centaines), ou limitez seulement les chemins d’API.
Les chiffres ci-dessous sont des exemples. Dimensionnez selon le poids de vos pages et le nombre de clients. Rien n’est imposé.
Pour Traefik, c’est le burst du middleware rateLimit :
http:
middlewares:
watchflare-ratelimit:
rateLimit:
average: 300 # example, tune to your setup
burst: 300 # example, tune to your setup Pour Nginx, limit_req avec un burst suffisant pour un chargement complet (nodelay pour servir le burst tout de suite, sans 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; # example, tune to your setup
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 : rien à régler par défaut. Si vous en ajoutez une via plugin, soyez généreux ou bornez-la aux chemins d’API — même principe.
Après le setup
Une fois le HTTPS en place, mettez à jour le .env pour que les cookies de session portent bien Secure :
COOKIE_DOMAIN=watchflare.example.com Détail cookies et vérif : configuration HTTPS.