Most instances are simpler behind one domain, with a single certificate and a single address
for every client. See Run behind one domain for copy-pasteable
Caddy and nginx configuration. The two-hostname layout on this page is still fully supported.
Steps
1
Put both services behind a reverse proxy with HTTPS
Use nginx, Caddy, Traefik, or a managed platform like Render or Fly, and proxy each service to a hostname you control:
app.your-domain.comto the client on:3000api.your-domain.comto the signaling server on:3001
localhost, a browser runs the web app without the screen wake lock, the clipboard API, and service workers, so the screen can sleep during a long transfer.The signaling server uses WebSocket connections. Make sure your proxy forwards the Upgrade and Connection headers.The steps below set values in the .env file of a Docker Compose stack. On a managed platform, set the same values as each service’s own variables instead: SOCKET_URL on the client, and CLIENT_URL and TRUSTED_PROXY_COUNT on the server.2
Set NEXT_PUBLIC_SOCKET_URL
In Nothing is rebuilt for this. Compose maps the value onto the client container’s
.env:.env
SOCKET_URL, and the browser reads it from GET /api/config on every page load.3
Set CLIENT_URL
In This adds your client origin to the server’s allow-list. It is matched exactly, including the scheme and with no trailing slash. Without it, HTTP requests from the web app on your domain are refused with a
.env:.env
403, and its signaling connection is refused too.4
Set TRUSTED_PROXY_COUNT
Set this to the number of proxy hops in front of the signaling server, so the per-address rate limits read real client addresses instead of the proxy’s:One reverse proxy is
.env
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. Left at 0 behind a proxy, every user counts as the proxy’s address and shares one set of limits, and the room seal that keeps a third network out of a link stops working. Setting it higher than your real hop count lets clients forge X-Forwarded-For and bypass the limits. Keep ports 3000 and 3001 from being reached around the proxy as well: see TRUSTED_PROXY_COUNT.5
Recreate the containers
Terminal
.env only when it recreates a container, so run this again after any later change to that file.6
Point the CLI and the desktop app at your instance
A split deployment has two addresses, so both clients need both. The CLI takes them as flags, or as Without
FLOE_SERVER and FLOE_WEB in the environment:Terminal
--web, the share link points at the signaling API rather than the web app, because a self-hosted server is otherwise assumed to serve both from one origin.In the desktop app, open Settings and, under Advanced, expand Server. Fill in both Server address (https://api.your-domain.com) and Share link address (https://app.your-domain.com), then click Test. See Desktop app settings.Request links are built from Share link address as well, and they stay off until the server’s policy file turns them on. See Turn on request links.Origins the server accepts
A browser is accepted only from an origin on the server’s allow-list. The list always holds the officialfloe.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 pages served from your domain and from floe.one are both allowed. If you are running a private fork and want a strict allow-list, remove the hard-coded entries from server/server.js.
Two more cases are accepted. A request with no Origin header gets through, which covers the plain HTTP calls the CLI and the desktop app make. A WebSocket whose Origin names the same hostname as the request’s Host header gets through too, which is how the CLI and the desktop app connect to api.your-domain.com. When Host carries a port other than the scheme’s default, the port has to match as well. That second rule is why the proxy has to pass Host through.
A refused origin, or a proxy that does not forward /ws, looks different on each side:
The CLI lines end in a hint from
floe newer than 1.11.0; floe 1.11.0 and earlier stop after
websocket: bad handshake. Floe Desktop 0.3.0 shows the /ws line, the second of its three, for
every refusal.
A browser shows no message of its own for a refused origin. The web app’s badge
stays Offline.
If something goes wrong
- The web app loads but its badge stays Offline: see The browser cannot connect to my signaling server.
- The browser reports a CORS failure and the server logs
Refused a request with 403.: see Browser requests are blocked, and the server answers HTTP 403. - Test in Floe Desktop stops at the realtime connection: see The Test button says the realtime connection was refused.