# Running behind a reverse proxy The restic-manager server is HTTP-only by design. TLS termination, public hostname, ACME, HSTS, and edge-level rate limiting all belong to a reverse proxy you already operate outside this project. ## What the proxy must forward The server reads four headers when (and only when) the immediate peer matches `RM_TRUSTED_PROXY`: | Header | Value | Why | |------------------------|----------------------------------------------------|-----| | `X-Forwarded-For` | The original client IP | Rate-limit keys, audit log entries, OIDC redirect-URI checks. | | `X-Forwarded-Proto` | `https` | Used for absolute URLs (e.g. OIDC redirect URIs). | | `Host` | The public hostname clients use | Cookies are scoped to this; `RM_BASE_URL` must match. | | `Connection` / `Upgrade` | Pass through unchanged | `/ws/agent` and `/api/jobs/{id}/stream` are WebSockets; without `Upgrade: websocket` they fail. | Set `RM_TRUSTED_PROXY` to the CIDR (or comma-separated list of CIDRs) the proxy connects from. Anything outside that range has its `X-Forwarded-*` headers ignored, so a stray request that bypasses the proxy can't spoof the client IP. ## Caddy ```caddyfile restic.example.com { encode zstd gzip reverse_proxy 127.0.0.1:8080 { header_up X-Real-IP {remote_host} } } ``` Caddy adds `X-Forwarded-For` / `X-Forwarded-Proto` automatically and passes WebSocket headers through by default, so this is the whole config. ## nginx ```nginx server { listen 443 ssl http2; server_name restic.example.com; ssl_certificate /etc/letsencrypt/live/restic.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/restic.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; 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 https; # WebSocket upgrade proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Long-lived agent WS — disable read timeout for this surface. proxy_read_timeout 86400s; } } ``` ## Traefik ```yaml http: routers: restic-manager: rule: "Host(`restic.example.com`)" entryPoints: [websecure] tls: certResolver: letsencrypt service: restic-manager services: restic-manager: loadBalancer: servers: - url: "http://restic-manager:8080" passHostHeader: true ``` Traefik forwards WebSocket upgrades and the standard `X-Forwarded-*` set out of the box. ## Verification After bringing the proxy up, the audit log should show your real client IP for an interactive login (not the proxy's local address). If you see `127.0.0.1` or the proxy's container IP, your `RM_TRUSTED_PROXY` is wrong or `X-Forwarded-For` isn't being forwarded.