https://floe.example.com. It is for anyone taking the Docker stack to production, and it is the
recommended layout: one certificate, one DNS record, and share links that need no extra setting.
The short version
Send/socket.io/, /ws, /api/, and /health to the signaling server, except /api/config,
and send everything else to the web client. Leave NEXT_PUBLIC_SOCKET_URL empty, set CLIENT_URL
to your domain, and set TRUSTED_PROXY_COUNT to 1. Then check the result with the commands under
Verify it works.
Before you start
- A domain whose DNS record points at this machine.
- Ports 80 and 443 open to the internet, for visitors and for the HTTPS certificate.
- The Docker quickstart stack running on this machine.
- Caddy or nginx installed on the same machine as Docker. The samples below reach the two
containers at
localhost:3000andlocalhost:3001.
localhost inside it is the proxy’s own container, and every route answers 502. Point it at
the host’s address instead, or join it to the Compose network and use the service names
client:3000 and server:3001.
Route table
The client and the signaling server claim different paths, so they coexist on one origin with no conflicts. Route them like this./api/config is the one exception to the /api/ rule, and it needs its own
route in both configs below. Send it to the signaling server and it returns 404.
With the empty socket URL this page recommends below, the browser still lands on
its own origin, which is the right answer, so the mistake never shows. Any
instance that sets a non-empty socket URL loses it silently.
Verify it works shows how to check it.
A server image of version 1.11.0 or earlier answers such an upgrade with nothing at all, and the
connection closes about 1 second later.
Proxy configuration
- Caddy
- nginx
Caddy handles HTTPS certificates and WebSocket upgrades automatically, so the
whole config is a handful of lines.
Caddyfile
Floe configuration
Leave the socket URL empty. The browser then talks to whatever host it was loaded from, which behind this proxy is already the right answer..env
Terminal
CLIENT_URL is matched exactly, including the scheme and with no trailing
slash. https://floe.example.com and https://www.floe.example.com are
different origins to the CORS check, so use whichever one your users actually
visit.
TRUSTED_PROXY_COUNT must equal the number of proxy hops in front of the
signaling server. One reverse proxy means 1. A CDN in front of that proxy means
2. Setting it higher than your real hop count lets clients forge
X-Forwarded-For and slip past the per-address rate limits. Leaving it at 0,
which is what Compose passes when .env does not set it, makes every user count
as the proxy’s own address. All of them then share one set of limits, and the room seal that
keeps a third network out of a link stops working. With the count at 1, also keep ports 3000 and
3001 from being reached around the proxy: see
TRUSTED_PROXY_COUNT.
Using the CLI and the desktop app with a one-domain instance
One address is all the CLI needs. The share link it prints resolves to the same origin, so it opens the web client correctly with no extra flag:Terminal
--web flag only exists for split deployments where the client lives at a
different address from the signaling server.
The desktop app takes the same single address. Open Settings and, under
Advanced, expand Server. Put https://floe.example.com in Server
address and click Test. The test runs three checks in order: that
/health answers as a Floe signaling server, that the realtime /ws connection
is accepted, and that GET /api/turn-credentials returns a usable connection
list. A proxy that forwards only some of those is named in the failure message
instead of breaking silently later.
A server with no TURN relay still passes, with “Connected, but this server has
no relay: Hide my IP will not work, and some networks will not connect”. Floe Desktop 0.3.0 says
“Connected, but there’s no relay for Hide my IP” instead. That matters to
Hide my IP address and to any two devices that cannot reach each other
directly. Leave Share link address blank on a one-domain instance and the app derives the link
from the server address. See Desktop app settings.
The test deliberately does not follow redirects. If you enter an http://
address and the proxy redirects it to https:// on the same host, as the nginx
sample above does with a 301, the test reports “Error: That address redirects to
https://. Start it with https:// and test again”. Change the address to
https:// and click Test again. Any other redirect on /health is reported
as “Error: That address answered HTTP 301 (likely the web app, not the server)”,
with the status the proxy sent, rather than passing. Floe Desktop 1.0.4 and earlier
report that HTTP line for a redirect to https:// as well.
On this layout the address both apps use is your CLIENT_URL, which the server always accepts, so
the Host header does not affect them here. It matters on the
two-hostname layout.
Request links are off on a new server until its policy file turns them on. On
this layout they need nothing else from Floe Desktop: the blank Share link
address already points at the web client, which serves the page a sender opens.
See Turn on request links.
TURN does not go through the proxy
If you run the optional coturn relay, its ports stay directly exposed. TURN is not HTTP, so a reverse proxy cannot carry it. See TURN relay for the port list and firewall rules.Verify it works
Terminal
{"socketUrl":"","commit":null}. An HTML page that
reads Cannot GET /api/config means the request reached the signaling server, so your proxy sends
/api/config to the wrong place.
Then open https://floe.example.com in a browser. The connection badge should
read Ready. If it reads Offline, the /socket.io/ route is the first thing to check.
The signaling server tells browsers to use HTTPS for its own hostname for one year
(Strict-Transport-Security), so a browser that has opened your instance over HTTPS refuses plain
HTTP for that hostname afterward. A server image of version 1.11.0 or earlier extends that to every
subdomain under the hostname as well.
Prefer two subdomains?
Splitting the client and server acrossapp.your-domain.com and
api.your-domain.com is still fully supported. See
Production deployment.
If something goes wrong
- The page loads but the badge stays Offline: see The browser cannot connect to my signaling server.
- Test in Floe Desktop stops at the realtime connection: see The Test button says the realtime connection was refused.
- People are turned away with
429or “Rate limit exceeded” becauseTRUSTED_PROXY_COUNTdoes not match your proxy: see 429, or Rate limit exceeded.