Troubleshooting FAQ
Connection issues
Guest: room not found or expired: xxx
- Wrong room code (case-sensitive)
- Room expired (default 12h) or closed by the host's Ctrl-C
- Ask the host to run
game createagain for a fresh code
Guest: UDP echo timed out
The client cannot reach the server's UDP probe port:
- Check
8080/udpis opened (cloud security group and OS firewall) - Check
signaling_addrpoints at the right server - Verify with
nc -u SERVER-IP 8080or any UDP client sendingECHO test
Stuck at UDP hole punching failed, falling back to relay ...
Punch failure is normal for strict NATs and does not break usage — it falls back to relay automatically. To get direct:
- Try a larger
--spread(symmetric NAT) - Make sure UDP outbound works on both sides
- If both ends are behind the same NAT/LAN, public-IP punching is a hairpin case and may fail — use
--relay
Direct link established but no data flows
- Confirm the host's
--serviceprocess is running and listening on the right port - Confirm the guest's
--listenport is not occupied - Run with
--verboseand check whether frames are flowing
decryption failed (wrong --key?)
The two sides' --key passphrases differ (or one side omitted it). Align them and recreate the room.
Deployment issues
Server: port already in use
AddrInUse8080or8081is taken by another process; change ports:bashfrp-sh serve --addr 0.0.0.0:9000 --relay-addr 0.0.0.0:9001- Update the client config accordingly (
http://IP:9000,IP:9001)
Windows: quick restart fails with a socket permission error
After a TCP listener closes, Windows briefly holds the port in a transitional state; immediately re-binding UDP on the same port can fail:
- Wait 2–3 seconds before restarting
- Or use a separate UDP probe port (
signaling_udp+ a dedicated server listener)
Performance & stability
Slow transfers
- The reliable stream window is 32 × 1200 B ≈ 38 KB in flight; high-latency links are throughput-limited
- Relay mode is capped by the server's egress
- Direct punching is generally faster (one less hop)
Connection dies after long idle
NAT mappings expire (usually 30–120 s). frp-sh sends keepalive ACKs every second, so this normally doesn't happen; if your NAT has an extremely short timeout, keep a trickle of data flowing.
Frames dropped under heavy load
The read buffer caps at 1 MB; beyond that frames are dropped and recovered by retransmit (go-back-N flow control). Expected behavior — correctness is unaffected.
Security questions
Is relay mode safe?
Relay traffic is plaintext (it passes through your signaling server). For confidentiality:
- Prefer direct punching +
--key - Or wrap the path to the server yourself (e.g., WireGuard)
Can room codes be guessed?
The code body is 6 hex chars (~16M combinations) with a 12h TTL; brute-forcing means hitting the server millions of times in a short window — low risk but nonzero. For higher assurance:
- Use a longer custom prefix:
frp-sh game create --prefix my-long-room-2024 - Add
--key(even if the code leaks, the traffic stays encrypted)
Other
Both sides are on the same WiFi — how do we get a LAN direct link?
No action needed — it's automatic. The host advertises its LAN addresses when creating a room; the guest punches at both the public and LAN addresses. On the same subnet you get 本地局域网直连 (LAN direct) within seconds — traffic stays entirely on the LAN, no server involved, lowest latency (great for gaming).
Can the guest reach other devices on the host's LAN (NAS/printer)?
Yes — use the lan series (mesh, Tailscale-like). The host's frp-sh lan create advertises its LAN subnets (e.g. 192.168.1.0/24) and enables IPv4 forwarding; the guest's frp-sh lan join automatically adds routes via its virtual NIC, then can ping / access devices on the host's LAN (both sides need root/admin). If both sides are on the same subnet, that subnet is skipped automatically to avoid route conflicts.
game / dev are pure port forwarding and do not provide access to the peer's LAN.
Why is the default port 25565? Is that Minecraft's?
Yes — 25565 is the default Minecraft (Java Edition) server port. frp-sh was designed for "play with friends" scenarios, so both the host service and the guest listen default to 25565 for out-of-the-box server hosting.
Any port works: the host sets --service to its service address, the guest sets --listen to its local port. For example, a web service on port 3000:
frp-sh game create --service 127.0.0.1:3000
frp-sh game join game-a3f9c2 --listen 127.0.0.1:3000invalid room id rules
Format: prefix-6hex (e.g., game-a3f9c2). --prefix keeps only lowercase alphanumerics and -_, max 16 chars.
How does a session end?
By default sessions reconnect automatically (backoff from 2s, capped at 15s), so link jitter won't end them. A session truly ends when:
- Either side presses
Ctrl-C(the host deletes the room on exit) - The room expires or is deleted (both sides end automatically)
--max-connsis exhausted — the current round ends, then it reconnects and waits for the next round
IPv6 support?
Signaling, relay, and punching all use standard SocketAddr and support IPv6 ([::1]:8080 form in config). Punching behavior depends on the actual network.
Can it run in Docker?
Yes — the static binary drops straight into an image:
FROM ubuntu:24.04
COPY frp-sh /usr/local/bin/frp-sh
EXPOSE 8080/tcp 8080/udp 8081/tcp
CMD ["frp-sh", "serve", "--addr", "0.0.0.0:8080", "--relay-addr", "0.0.0.0:8081"]Feedback
If something is not covered, report it with frp-sh --verbose output and a description of the network environment.