W Watchflare docs
Cette page n'est pas encore disponible en français. Vous lisez la version anglaise.

Reverse proxy

Configure Traefik, Nginx, or Caddy in front of the Watchflare Hub.

The Hub exposes two ports with different proxying requirements:

PortProtocolProxy type
8080HTTPStandard reverse proxy — TLS termination here
50051gRPC / TLS 1.3TCP passthrough — no TLS termination

Warning

The gRPC port must be proxied at the TCP level, without TLS termination. Agents pin the Hub’s CA certificate at registration. If the proxy presents a different certificate, every agent will refuse to connect.


Traefik

Tested: ✅ Traefik v3

Traefik needs two routes: an HTTP reverse proxy for the dashboard, and a TCP passthrough for gRPC.

Static config

traefik.yml yaml
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  grpc:
    address: ":50051"

certificatesResolvers:
  letsencrypt:
    acme:
      email: you@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

Restart Traefik after editing the static config. Dynamic config changes are hot-reloaded.

Dynamic config

rules.d/watchflare.yml yaml
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"

Replace HUB_IP with the IP or hostname of the server running the Hub, and watchflare.example.com with your domain.

Docker Compose labels

If Traefik and the Hub run in the same Compose stack:

docker-compose.yml yaml
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

Tested: ⚠️ Not tested — configuration based on Nginx documentation

Nginx requires the stream module for TCP passthrough on the gRPC port (--with-stream at compile time, included in most Linux distributions).

nginx.conf nginx
# 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;
    }
}

The stream {} block must be at the top level of nginx.conf, not inside an http {} block.

Warning

proxy_http_version 1.1, proxy_set_header Connection "", and proxy_buffering off are all required in the HTTP block. Without them, the SSE stream that drives real-time host status and metrics updates will be buffered and the dashboard will not update live.


Caddy

Tested: ⚠️ Not tested — configuration based on Caddy documentation

Caddy handles HTTPS and certificate renewal automatically. For the gRPC TCP passthrough, the caddy-l4 plugin is required.

HTTP (dashboard) — standard Caddyfile, no plugin needed:

Caddyfile caddy
watchflare.example.com {
    reverse_proxy HUB_IP:8080
}

gRPC TCP passthrough — requires caddy-l4. Add a layer4 block in a JSON config or via the xcaddy build with the plugin. Refer to the caddy-l4 documentation for syntax.


Why HostSNI("*")

During the TLS handshake, the agent sends an SNI equal to tls_server in agent.conf — this defaults to watchflare (the certificate CN generated by the Hub). A rule like HostSNI("watchflare") would need to match this value exactly.

Using HostSNI("*") avoids mismatches if the certificate CN changes (e.g. when switching to TLS_MODE=custom with a different CN). It is safe because port 50051 is dedicated to Watchflare gRPC — security is enforced by TLS 1.3 and per-request HMAC authentication, not by SNI filtering.


After setup

Once HTTPS is working, update your .env to ensure session cookies are correctly marked Secure:

.env bash
COOKIE_DOMAIN=watchflare.example.com

See HTTPS setup for details on cookie security and how to verify it works.