> ## 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.

# Production Deployment for Self-Hosted Floe

> Deploy a self-hosted Floe instance in production with HTTPS, a reverse proxy, custom domain, WebSocket support, and secure TURN relay credentials.

For a deployment accessible beyond a single machine, follow these steps.

<Note>
  The simplest production layout puts the client and the signaling server behind
  **one** domain, so there is a single certificate and a single URL to configure.
  See [Reverse Proxy](/docs/self-hosting/reverse-proxy) for copy-pasteable Caddy and
  nginx configuration. This page covers the two-subdomain layout, which is still
  fully supported.
</Note>

## Steps

<Steps>
  <Step title="Put both services behind a reverse proxy with HTTPS">
    Browsers require a secure context (HTTPS) for WebRTC and the Screen Wake Lock API. Use Nginx, Caddy, Traefik, or a managed platform like Render or Fly.

    Proxy each service to a hostname you control:

    * `app.your-domain.com` to the client on `:3000`
    * `api.your-domain.com` to the signaling server on `:3001`

    The signaling server uses WebSockets. Make sure your proxy forwards the `Upgrade` and `Connection` headers.
  </Step>

  <Step title="Set NEXT_PUBLIC_SOCKET_URL and rebuild">
    In `.env`:

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

    Then restart. No rebuild: the client reads this address at runtime.

    ```bash theme={null}
    docker compose up -d
    ```
  </Step>

  <Step title="Set CLIENT_URL">
    In `.env`:

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

    This adds your client origin to the server's CORS allow-list. Without it, browser requests from your domain will be blocked.
  </Step>

  <Step title="Set TRUSTED_PROXY_COUNT">
    Set this to the number of proxy hops in front of the signaling server so per-IP rate limiting reads real client IPs instead of proxy IPs:

    ```env theme={null}
    TRUSTED_PROXY_COUNT=1
    ```

    One reverse proxy is `1`, a CDN in front of that proxy is `2`. The server's own default is `1`; Docker Compose passes `0` explicitly, which is correct only when the container ports are published directly. Setting it higher than your real hop count lets clients forge `X-Forwarded-For` and bypass the per-IP rate limits, so if you expose the server directly with no proxy, set `0` deliberately rather than leaving it unset.
  </Step>
</Steps>

## CORS note

The server's allow-list also includes the official `floe.one` origins (`https://floe.one` and `https://www.floe.one`) plus `http://localhost:3000` for local development. Your `CLIENT_URL` is added on top of these, so browser clients on your domain and on floe.one can both reach your server. If you are running a private fork and want a strict allow-list, remove the hard-coded entries from `server/server.js`.
