In the browser
”Open in your browser”
Floe is running inside another app’s built-in browser, and those handle file transfers poorly. Follow the two steps on screen, or tap Copy link to paste in browser and open it in Chrome, Safari, Edge, or Firefox. Continue anyway (some features may not work) proceeds regardless. Until you do one of those, the page has not joined the room, so nothing is lost by switching. See Receiving files.The transfer never starts
Both sides have to be present at the same time, and the connection is made directly between them, so several things can hold it up.- The sender left. Closing the browser tab, quitting Floe Desktop, or stopping
floe sendends the session. Nothing resumes it. - The recipient has not actually opened the link. In the browser and the desktop app the
transfer then starts by itself.
floe receiveasks first, so a sender can sit on “Connecting” while the recipient has not answered theAccept? [Y/n]prompt. Pass-yto skip it. - The network will not allow a direct path, and the relay is not available. In the browser
this is the sender’s Network relay fallback checkbox, on by default. A browser recipient has
no such control, so ask the sender. On the CLI the equivalent is not passing
--no-relay. Floe Desktop has no relay-fallback control at all, so the relay is always available there.
”Link Invalid”
The card reads “This link is either expired, already in use, or does not exist.” A room takes exactly one recipient, so the most common cause is that someone is already connected on this link, including you in another tab. Opening a link does not use it up, so closing the other tab frees the seat. Otherwise ask the sender for a new link.”Could not connect. Ask the sender to enable “Network Relay” and try again.”
This is the receiver’s message for every failure Floe treats as expected, so the relay is not always the culprit. The sender may have turned their Network relay fallback checkbox off, runfloe send --no-relay, or simply closed their tab.
”Connection failed. Enable “Network Relay” to connect across restrictive networks.”
The sender’s half of the same situation. The only available path was relayed and relay fallback was off, so Floe closed the connection rather than sending over a route you excluded.”Transfer blocked. Relay limit exceeded.”
The connection ended up relayed and the queued files total more than 2 GB. Nothing was sent. Remove files until the total is at or under the cap, or get a direct connection, which has no limit: use a normal home or personal network, disconnect from any VPN, avoid strict corporate and university networks, or try a personal hotspot. Exactly 2 GB is allowed; only more is refused. See File size limits.”Peer disconnected. Waiting for reconnection”
The other side dropped before the first file arrived. It does not resume by itself. If your network blipped before any file landed, Floe rejoins the room on its own and the sender builds a fresh connection. Once files have started landing, signaling is finished and Floe stops rejoining, so a drop after that point ends the session.”Too many refreshes. Reconnecting”
You have hit the per-IP connection limit: 30 new connections per minute. Wait a minute. This is usually rapid page reloads, or several devices behind one address. On a self-hosted instance raiseMAX_CONNECTIONS_PER_IP.
”Transfer timed out waiting for receiver. Please try again.”
The sender announced a file and the other side never acknowledged it within two minutes. Usually the recipient closed the tab, or their connection died between connecting and receiving.”Could not prepare files for ZIP.” or “ZIP creation failed.”
Both suggest Download All instead, and that is the right move. Zipping holds every file in memory at once, so a large multi-file transfer can exhaust a phone or a tab. Saving files from their own rows uses far less memory.Downloads behave oddly on iOS
Use Download ZIP when there is more than one file. Saving them individually behaves inconsistently in Safari and iOS web views. The two bulk buttons appear only for multi-file transfers, and they come and go: they show as soon as the second file lands, hide while the next file streams, and settle once the transfer finishes. A single file is saved from the download button on its own row.A file did not appear at all
Floe compares what arrived against the size the sender announced and discards anything that does not match, in either direction, rather than handing you a file that looks whole and is not. Ask the sender to send that file again.On the command line
code "olive-tiger-castle" not found or expired (codes expire after 10 minutes)
The code was mistyped, or it has passed its ten minutes.
Codes are lowercase words joined by hyphens, but capitalization does not matter: floe receive
and Floe Desktop lowercase what you type before the lookup, so Olive-Tiger-Castle resolves the
same as olive-tiger-castle. Surrounding spaces are trimmed and capitalization is folded for
you; nothing else is. Ask the sender to start a new session for a fresh code.
this code is no longer active; ask for a new one
The code resolved, but nobody is sharing on it. The sender finished, quit, or their session ended,
and joining the empty room they left behind is what produces this message. A word code stays valid for its
full ten minutes, and a pasted share link does not expire at all. Either way it now points at a
room nobody is in, so ask for a new one.
room is full (someone else may already be receiving)
Someone else joined this transfer first. A room holds exactly two peers. Ask for a new code.
timed out establishing a connection
Thirty seconds passed without a usable path. Either the other side never joined, or neither side
could reach the other and no relay was available.
If you passed --no-relay, this is what that looks like when no direct path exists: the flag
removes the relay from the address list, so there is nothing else to try and the failure is this
generic timeout rather than a message about the relay.
--relay-only used to fail the same way against a server with no TURN relay. It no longer
reaches this timeout: the flag is checked against the server’s connection list before the
transfer starts, and the command stops at once naming the flag. See
--relay-only needs a TURN relay.
--relay-only needs a TURN relay
The full line names the server it asked:
--relay-only needs a TURN relay and https://floe.example.com offers none; drop the flag, or configure a relay on the server.
--relay-only routes everything through the relay and never tries a direct path, so a server
that offers no relay leaves it nowhere to go. The check runs before the transfer starts, so this
arrives immediately instead of as a timeout half a minute later. Drop the flag (and unset
FLOE_RELAY_ONLY if you set it), or add a relay to the instance. Floe’s own server always has
one, so this points at a self-hosted address. See TURN relay.
A second wording, the connection details from ... could not be read, means the server was never
heard from, so floe fell back to public STUN and cannot say what the server offers. Check
--server for a typo, check that a reverse proxy forwards /api/, or try again in a minute:
that endpoint rate-limits repeated requests from one address.
connected, but no data arrived from the sender within 30s
You reached the sender and they never started sending. Ask them to try again.
connection closed while waiting for the receiver (transfer declined or receiver exited)
The other side answered n at the Accept? [Y/n] prompt, or their process ended.
timed out waiting for delivery confirmation from peer
Every byte was sent, but for a full minute the receiver neither confirmed delivery nor took in any
more of the data still on its way. The connection has most likely stalled. Check with the recipient
before resending.
relay connections are capped at 2 GB
The same cap as in the browser, from the sending side. See
File size limits.
Cannot transfer: your floe is too old for this peer
Also worded Cannot transfer: peer's floe is too old. Peers on different Floe versions normally
transfer fine, so a small version difference is never the cause. This appears only when a release
changed the transfer protocol in a way the two versions cannot bridge.
The message prints both sides as You: and Peer: with their version strings. Update the older
one:
Terminal
Cannot update a dev build.
The binary was built from source or installed with go install, so it carries no version to
compare and cannot replace itself. Install through a package manager or the install script for a
release build with working self-update. See Install the CLI.
installed via homebrew - run 'brew upgrade floe' to update
floe update refuses to replace a binary your package manager owns. Use the command it names.
Detection is a substring match on the binary’s path for homebrew, cellar, linuxbrew,
scoop, or winget, so a script install living under a directory with one of those words in it
is treated the same way.
Warning: could not reach signaling server for TURN credentials. Using STUN only.
Floe could not fetch relay credentials and is carrying on with direct connections only. On a
restrictive network the transfer will probably fail. Check the server address, and check
FLOE_SERVER in your environment if you did not pass --server.
no files to send
Every path you gave resolved to nothing, or to an empty folder. Floe checks before touching the
network.
Slow to connect when a VPN or virtual machine is running
A VPN such as Tailscale, or virtualization software such as VMware, VirtualBox, WSL, or Hyper-V, adds network interfaces. The CLI probes each one, so a machine with several can spend a long time on “Connecting…” before settling. The transfer still completes, and the browser is unaffected. Floe already ignores link-local addresses, which covers dead auto-configured adapters. A VPN adapter with a routable address, such as Tailscale’s100.x range, is not filtered. Pin the CLI
to the interface you mean:
Terminal
A browser recipient cannot join a CLI transfer
A browser can only join by opening the link, never by typing a code. Share the link thatfloe send prints with browser recipients, and the code with CLI or desktop recipients, both
of which also accept the link.
In Floe Desktop
Every failure in the app is shown with anError: prefix, so a message on screen reads
Error: That code was not recognized. ....
”That code was not recognized. Check it for typos, or ask the sender for a new one.”
The same causes as the CLI’s expired-code message: a typo, or more than ten minutes have passed. It also appears when the code belongs to a different server from the one the app is set to.”Connected, but the sender never started sending. Ask them to try again.”
Floe reached the sender and waited thirty seconds with nothing arriving.”Could not reach the server. Check your internet connection.”
The app could not reach the signaling server at all, or the server never assigned it a role within twenty seconds. If you recently changed Server address, check it with Test.The Test button says the realtime connection was refused
“The server answered, but the realtime connection was refused. If it is behind a reverse proxy, check that /ws is being forwarded.” The address is a Floe signaling server but the WebSocket upgrade at/ws is not reaching it, which is nearly always a proxy that forwards ordinary
requests without forwarding the upgrade. See
Run behind one domain.
The Test button says the API answered with an HTTP error
“The server is running, but its API answered with HTTP 404.”/api/ is not being forwarded. Fix
it before relying on the server, because nothing else reports this failure: relay credentials fall
back to public STUN and the room code silently fails to register, so you get a share link with no
code and no error.
The Test button says the server has no TURN relay
“Connected. This server has no TURN relay, so Hide my IP will not work.” The address is a working Floe signaling server, and this is a pass rather than a failure, which is why it carries noError: prefix. Hide my IP address cannot work at all
without a relay, and ordinary transfers succeed only where the two devices can reach each other
directly. Turn the switch off, or add a relay to the instance. See
TURN relay.
”Hide my IP needs a TURN relay and this server has none.”
The server answered, and its answer had no relay in it. The transfer stops before it starts rather than timing out about thirty seconds later with advice about both devices being online. Turn Hide my IP off, or add a relay to your instance. Floe’s own server always has one, so this only appears on a self-hosted address. Test reports the same thing about an address before you rely on it. See TURN relay.”Hide my IP needs a TURN relay, and this server’s connection details could not be read.”
The difference from the message above is who is being blamed. Here the server was not heard from at all, so Floe fell back to public STUN and cannot say what the server offers. Check the Server address for a typo, run Test against it, and if it is behind a reverse proxy check that/api/ is being forwarded. Trying again in a minute also helps, because the TURN endpoint
rate-limits repeated requests from one address. Turning Hide my IP off lets the transfer
proceed over whatever path is available.
The Test button says it may be the web app rather than the signaling server
Something answered but not with a health check. In a default self-hosted setup the web app is on port 3000 and the signaling server on 3001, sohttp://localhost:3000 produces this and
http://localhost:3001 is what you want. Server address always wants the signaling server.
Test never follows redirects, so a captive portal or a catch-all rewrite cannot pass itself off as
a healthy server.
There is no right-click menu setting
The Windows section, and with it Show in right-click menu, is hidden in the Microsoft Store build. A packaged app cannot register the entry in a way File Explorer would read, and asking for it returns “the right-click menu is not available in the Microsoft Store build”. Install the build from GitHub if you need it.The switch for the right-click menu keeps turning itself off
The registry write was refused, which usually means a managed or locked-down machine.Every transfer is capped at 2 GB
Hide my IP address routes every transfer through the relay, which is where the cap lives, so it applies even on a network that could have connected directly. An oversized send fails with “Turn off Hide my IP to send larger files”. See Hide my IP address.Floe will not start
If the Microsoft Edge WebView2 runtime is missing and the machine cannot reach Microsoft’s download, Floe cannot start and says so. Install WebView2 yourself, then start Floe again. See Install the desktop app.On a self-hosted instance
The browser cannot connect to my signaling server
Set the signaling address and recreate the container. No rebuild is needed, because the client reads it at runtime:Terminal
NEXT_PUBLIC_SOCKET_URL into the container as SOCKET_URL, which is the name the
running client reads. Outside Compose, set SOCKET_URL on the client process. Setting
NEXT_PUBLIC_SOCKET_URL on an already-built client does nothing, because Next inlines that name
at build time.
Check what the client is actually serving:
Terminal
socketUrl means the browser will use its own origin, which is correct only when both
services sit behind one reverse proxy. Otherwise confirm the URL is
reachable from your users’ browsers: http://localhost:3001 only works when the browser is on the
same machine as the server.
Browser requests are blocked, and the server answers HTTP 500
A rejected CORS origin does not come back as a recognizable CORS error. The server answers500
with {"error":"Internal server error"} and logs the stack, while the browser reports a generic
CORS failure. If you see that pairing, check CLIENT_URL first.
It must match your client’s public origin exactly, including the scheme and with no trailing
slash, and it holds one origin. A comma-separated list matches nothing. https://floe.one,
https://www.floe.one, and http://localhost:3000 are always allowed in addition.
429, or “Rate limit exceeded”
Four independent per-IP limiters, each over a 60-second window: 30 connections shared across Socket.IO and WebSocket (MAX_CONNECTIONS_PER_IP), 20 TURN credential requests
(MAX_TURN_REQUESTS_PER_IP), 60 room-code requests (MAX_CODE_REQUESTS_PER_IP), and 60 stats
reports, which is the one limit with no environment override.
The three HTTP limiters answer 429 with Too many requests, or Too many reports for stats.
The connection limiter is not HTTP: it refuses the Socket.IO handshake and closes the WebSocket
with the reason Rate limit exceeded.
Two things to know before raising a limit. Only admitted connections count: a refused
connection attempt is not recorded, so a client that keeps retrying gets back in once its admitted
connections age out of the 60-second window, the same way the three HTTP limiters recover after a
429. And setting any MAX_* variable to 0 does not disable the limit; it silently restores the
default.
Behind a reverse proxy, set TRUSTED_PROXY_COUNT to the number of proxy hops so the server reads
real client IPs instead of the proxy’s. The server defaults to 1, but the Compose stack
publishes ports directly and passes 0, so a Compose deployment behind a proxy has to set it.
503 “Server busy, try again shortly”
POST /api/code returns this once MAX_ACTIVE_CODES codes are live at once, regardless of source
IP. It bounds the memory the code registry can use. Only expiry frees a slot: resolving a code
does not delete it, so a code used immediately still holds its slot for the full ten minutes.
The TURN relay is not working
Check which option your instance uses. Managed Cloudflare TURN. Confirm bothCLOUDFLARE_TURN_KEY_ID and
CLOUDFLARE_TURN_KEY_API_TOKEN are set, then:
Terminal
stun:stun.cloudflare.com:3478 one with no credentials,
and a turn:/turns: one that carries a username and credential. If it returns Google STUN
servers instead, one of the two variables is missing or the API token is invalid.
Credentials are cached in memory for 5 minutes. Both variables are read once at startup, so a fixed
key takes effect only after a restart. A key that breaks later is masked for a while: the server
keeps serving the last good copy for up to about 23 hours after it was minted before it falls back,
and logs Cloudflare TURN mint failed with a reason word (such as TimeoutError or status 401)
at most once every 5 minutes.
Self-hosted coturn. The bundled service uses host networking, so it needs a Linux host with a
public IP, a domain, and TLS certificates. Confirm TURN_SECRET and TURN_DOMAIN match coturn’s
static-auth-secret and realm. Setting only one of the pair falls through to STUN with no
warning.
coturn logs Default realm: localdomain
coturn is running on built-in defaults with no auth secret, and relaying silently fails for
exactly the peers it exists to serve. This happens when coturn/turnserver.conf did not exist
before the first docker compose up, so Docker created a directory at that path.
Run rmdir coturn/turnserver.conf first. Copying a file onto a directory succeeds and writes
it inside, so the obvious fix appears to work and does not. Then fetch the example config and
restart. See TURN relay.
The server logs nothing at all
That is expected. The signaling server has no access log and no startup banner, not even a “listening on” line, sodocker logs on a healthy server is empty. Use GET /health, which
returns {"status":"healthy","uptime":<seconds>}, to confirm it is alive and to see whether a
restart landed.