# Running behind a reverse proxy

The restic-manager server is HTTP-only by design (see `spec.md` §11):
TLS termination, public hostname, ACME, HSTS, and edge-level rate
limiting all belong to a reverse proxy that you already operate
outside this project. The reference compose in `deploy/docker-compose.yml`
stands up *only* the server; this page covers what your proxy needs
to do to make the rest of it work.

## 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 (single value, or comma chain)    | Rate-limit keys, audit log entries, and OIDC redirect-URI checks all use the real client IP. |
| `X-Forwarded-Proto` | `https`                                                  | The server emits absolute URLs (e.g. OIDC redirect URIs) using this. |
| `Host`              | The public hostname clients use                          | Cookies are scoped to this; `RM_BASE_URL` must match. |
| `Connection`/`Upgrade` | Pass through unchanged                                | The agent connects on `/ws/agent` and the live-log viewer connects on `/api/jobs/{id}/stream` — both are WebSockets and need `Upgrade: websocket` to survive the hop. |

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.

## Example: Caddy

```caddyfile
restic.example.com {
    # Caddy's default reverse_proxy preserves Host, sets
    # X-Forwarded-For/Proto, and passes Connection: upgrade through,
    # so a single directive covers HTTP + WebSocket.
    reverse_proxy 127.0.0.1:8080

    encode zstd gzip
}
```

`RM_TRUSTED_PROXY=127.0.0.1/32` if Caddy and the server share the
host; the docker-bridge CIDR (commonly `172.16.0.0/12`) if Caddy
runs in another container on the default bridge network.

## Example: nginx

```nginx
server {
    listen 443 ssl http2;
    server_name restic.example.com;

    ssl_certificate     /etc/ssl/restic.example.com.fullchain.pem;
    ssl_certificate_key /etc/ssl/restic.example.com.key.pem;

    location / {
        proxy_pass         http://127.0.0.1:8080;
        proxy_http_version 1.1;

        # WebSocket support — agent + live-log endpoints need this.
        proxy_set_header   Upgrade           $http_upgrade;
        proxy_set_header   Connection        $connection_upgrade;

        # Trusted-proxy headers.
        proxy_set_header   Host              $host;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto https;

        # Live job logs are long-running streams. Bump read timeouts
        # so nginx doesn't drop them mid-backup.
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;
    }
}

# Standard websocket upgrade map (define once at the http {} level).
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
```

`RM_TRUSTED_PROXY` for the same-host case: `127.0.0.1/32`.

## Example: Traefik (label-based)

```yaml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.restic-manager.rule=Host(`restic.example.com`)"
  - "traefik.http.routers.restic-manager.entrypoints=websecure"
  - "traefik.http.routers.restic-manager.tls.certresolver=letsencrypt"
  - "traefik.http.services.restic-manager.loadbalancer.server.port=8080"
```

Traefik handles `X-Forwarded-*` and `Connection: upgrade` by default.
`RM_TRUSTED_PROXY` should be the docker network the Traefik container
shares with the server (commonly `172.16.0.0/12` for the default
bridge, or whatever your overlay network's CIDR is).

## Sanity-checking the wiring

After bringing the stack up:

1. `curl -fsS https://restic.example.com/healthz` — should return 200.
2. The login page should report HTTPS in the address bar; cookies
   set after login should carry the `Secure` flag.
3. Check the server log for the `config resolved` line:
   `trusted_proxies` must include the IP/CIDR your proxy actually
   connects from.
4. Enrol a test agent — the WebSocket handshake hitting `/ws/agent`
   confirms `Upgrade` is being forwarded correctly.

If any of those fail, the proxy is the first place to look — the
server itself is intentionally minimal.
