> ## Documentation Index
> Fetch the complete documentation index at: https://www.floe.one/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Hosted Floe Configuration

> Environment variable reference for a self-hosted Floe instance, covering client build-time URLs, CORS, TURN credentials, and stats persistence.

All settings live in the `.env` file you copied from `.env.docker.example`.

## Variables

| Variable                        | Service          | Required   | Default                 | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------- | ---------------- | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_SOCKET_URL`        | client (runtime) | No         | `http://localhost:3001` | URL the **browser** uses to reach the signaling server. Read at runtime, so changing it needs only `docker compose up -d`, no rebuild. Set it **empty** when both services share one origin behind a [reverse proxy](/docs/self-hosting/reverse-proxy); the browser then uses its own. See [how the client finds the server](/docs/self-hosting/build-time-url).                                                                                                                                                                                                            |
| `FLOE_IMAGE_TAG`                | compose          | No         | `latest`                | Which published image tag to run, for example `1.9.1`, `1.9`, `main`, or `sha-fed7ebb`. See [Container Images](/docs/self-hosting/images). Not to be confused with `FLOE_VERSION`, which belongs to the CLI installer and carries a leading `v`.                                                                                                                                                                                                                                                                                                                       |
| `NEXT_PUBLIC_SITE_URL`          | client (build)   | No         | *(none)*                | Canonical public origin of your client (e.g. `https://app.your-domain.com`), used for canonical, Open Graph, JSON-LD, and sitemap URLs. Inlined at build time, so the published image cannot carry it and omits those tags instead of claiming `floe.one`. Set it only when building the client yourself.                                                                                                                                                                                                                                                         |
| `PORT`                          | server           | No         | `3001`                  | **Host** port the signaling server is published on. Inside the container it always listens on 3001; Docker Compose maps `${PORT}` to it. Change this and you must also update `NEXT_PUBLIC_SOCKET_URL` and rebuild the client, or browsers keep dialing the old port.                                                                                                                                                                                                                                                                                             |
| `CLIENT_PORT`                   | client           | No         | `3000`                  | **Host** port the web client is published on. Inside the container it always listens on 3000. Compose-only; it has no effect outside Docker.                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `NODE_ENV`                      | server           | No         | `development`           | Standard Node.js runtime flag. The Docker image sets this to `production` for you, so this default only applies when you run the server directly with Node. Set it to `production` if you do: Express only suppresses stack traces in its built-in error responses when the value is exactly `production`, and blank counts as unset.                                                                                                                                                                                                                             |
| `CLIENT_URL`                    | server           | Yes (prod) | *(none)*                | Frontend origin allowed by CORS. Matched exactly, including scheme and with no trailing slash. Use `https://floe.example.com` for a [one-domain deployment](/docs/self-hosting/reverse-proxy), or `https://app.your-domain.com` when the client has its own subdomain.                                                                                                                                                                                                                                                                                                 |
| `TRUSTED_PROXY_COUNT`           | server           | No         | `1`                     | Number of trusted reverse-proxy hops in front of the server, used for correct `X-Forwarded-For` parsing and per-IP rate limiting. Use `1` behind a single reverse proxy, `2` behind a proxy plus a CDN, and `0` when the server is exposed directly with no proxy. Docker Compose passes `0` explicitly, because publishing container ports directly is its default. Setting it above your real hop count lets clients forge `X-Forwarded-For` and bypass the rate limits, so set it to `0` deliberately rather than leaving it unset on a directly exposed host. |
| `MAX_CONNECTIONS_PER_IP`        | server           | No         | `30`                    | Maximum new connections allowed per IP per 60-second window, shared across Socket.IO and WebSocket. Raise this in staging or test environments that drive many connections from a single IP.                                                                                                                                                                                                                                                                                                                                                                      |
| `MAX_TURN_REQUESTS_PER_IP`      | server           | No         | `20`                    | Maximum `GET /api/turn-credentials` requests per IP per 60-second window. Requests over the cap get `429`, and the client silently falls back to STUN-only. Raise this in CI or staging suites that start many transfers from one IP.                                                                                                                                                                                                                                                                                                                             |
| `MAX_CODE_REQUESTS_PER_IP`      | server           | No         | `60`                    | Maximum requests per IP per 60-second window against the room-code endpoints, shared across `POST /api/code` and `GET /api/code/:code`. Requests over the cap get `429`. Raise this in CI or staging suites that mint many codes from one IP.                                                                                                                                                                                                                                                                                                                     |
| `MAX_ACTIVE_CODES`              | server           | No         | `10000`                 | Maximum number of simultaneously-live room codes. Once this cap is reached, `POST /api/code` returns `503` until existing codes expire (10-minute TTL) or are consumed. Bounds memory use of the code registry; raise it only if you legitimately expect more than ten thousand pending transfers at once.                                                                                                                                                                                                                                                        |
| `CLOUDFLARE_TURN_KEY_ID`        | server           | No         | *(none)*                | Turn Token ID of a [Cloudflare Realtime TURN](https://developers.cloudflare.com/realtime/turn/) key. With the API token below, the server mints short-lived TURN credentials from Cloudflare's managed network, so you need no public IP, extra ports, or TLS certificates. Takes precedence over the coturn variables.                                                                                                                                                                                                                                           |
| `CLOUDFLARE_TURN_KEY_API_TOKEN` | server           | No         | *(none)*                | API token that pairs with `CLOUDFLARE_TURN_KEY_ID`. Keep it secret.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `TURN_SECRET`                   | server           | No         | *(none)*                | Shared HMAC secret for self-hosted coturn credentials, used only when the Cloudflare variables are unset. If no TURN option is configured, the server returns STUN-only and no relay is offered. Must match coturn's `static-auth-secret`.                                                                                                                                                                                                                                                                                                                        |
| `TURN_DOMAIN`                   | server           | No         | *(none)*                | Public hostname of your self-hosted TURN server (e.g. `turn.your-domain.com`). Must match coturn's `realm`.                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `UPSTASH_REDIS_REST_URL`        | server           | No         | *(none)*                | REST URL of an [Upstash Redis](https://upstash.com) database, used to durably persist the global transfer counter shown on the homepage. If empty, the counter runs in memory only and resets to 0 on every restart.                                                                                                                                                                                                                                                                                                                                              |
| `UPSTASH_REDIS_REST_TOKEN`      | server           | No         | *(none)*                | REST token that pairs with `UPSTASH_REDIS_REST_URL`. Both must be set together for durable stats. Use the **read+write** token (the default one in the Upstash console), not the read-only token. The server needs write access to increment the counter; with a read-only token, writes are silently rejected and the total never persists.                                                                                                                                                                                                                      |
| `MAX_REPORT_BYTES`              | server           | No         | `5497558138880` (5 TB)  | Maximum bytes accepted in a single `POST /api/stats/report` request. Rejects implausibly large single-transfer reports.                                                                                                                                                                                                                                                                                                                                                                                                                                           |

## Minimal local configuration

For local testing, only one variable matters:

```env theme={null}
NEXT_PUBLIC_SOCKET_URL=http://localhost:3001
```

Everything else can stay at its default.

## Production configuration

A typical production `.env` for a deployment with HTTPS and TURN:

```env theme={null}
NEXT_PUBLIC_SOCKET_URL=https://api.your-domain.com
CLIENT_URL=https://app.your-domain.com
TRUSTED_PROXY_COUNT=1

# Managed TURN via Cloudflare (recommended)
CLOUDFLARE_TURN_KEY_ID=your-turn-token-id
CLOUDFLARE_TURN_KEY_API_TOKEN=your-api-token

# Or self-hosted coturn instead:
# TURN_SECRET=your-strong-random-secret
# TURN_DOMAIN=turn.your-domain.com
```

See [Production Deployment](/docs/self-hosting/production) for the full setup.
