• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • 2026 staff recruitment is open! Check it out and consider applying!

Cloudflare WebSocket Tunnel - Free for the Open Tibia community

Some replies are turning this into bedtime stories and random drama instead of focusing on the actual topic of the thread, and half of these stories only God knows if they are even true or not. The goal of this thread was simply to share something useful for the community. Many people are already testing it, improving it and finding it helpful, which is what really matters. If anyone has real feedback, fixes or test results, feel free to share them.

The upgraded version posted here already addresses several of the concerns mentioned regarding websocket handling, reconnects, cleanup and connection stability: Cloudflare WebSocket Tunnel - Free for the Open Tibia community (https://otland.net/threads/cloudflare-websocket-tunnel-free-for-the-open-tibia-community.304390/post-2802165)
 
I’m running some tests connecting through WS, following the “upgraded version” by Aizendazai.

One issue I’m facing is some random disconnections, as shown in the log below:
Bash:
[2026-05-28 18:09:48.929] [WS→TCP Error] [[game] 192.168.45.210: readfrom tcp 127.0.0.1:60214->127.0.0.1:7172: read tcp 104.26.14.87:2053->198.51.100.42:11873: read: connection reset by peer]
[2026-05-28 18:09:48.929] [Bridge] [[game] Closed connection for client: 192.168.45.210]
[2026-05-28 18:13:54.551] [WS→TCP Error] [[game] 192.168.45.210: readfrom tcp 127.0.0.1:48802->127.0.0.1:7172: websocket: close 1006 (abnormal closure): unexpected EOF]
[2026-05-28 18:13:54.551] [Bridge] [[game] Closed connection for client: 192.168.45.210]


I currently have “anti-idle” enabled on 3 clients (using OTCv8-OTA). I suspect some default Cloudflare timeout might be causing the disconnections. I’ll keep monitoring it and will post again as soon as I have more information.

In my test environment, I’m not using HAProxy, since I already have Real IP injection: ENABLED (PROXYv2).

I’m using a single VPS to run everything (OVH).
 
Last edited:
I’m running some tests connecting through WS, following the “upgraded version” by Aizendazai.

One issue I’m facing is some random disconnections, as shown in the log below:
Bash:
[2026-05-28 18:09:48.929] [WS→TCP Error] [[game] 192.168.45.210: readfrom tcp 127.0.0.1:60214->127.0.0.1:7172: read tcp 104.26.14.87:2053->198.51.100.42:11873: read: connection reset by peer]
[2026-05-28 18:09:48.929] [Bridge] [[game] Closed connection for client: 192.168.45.210]
[2026-05-28 18:13:54.551] [WS→TCP Error] [[game] 192.168.45.210: readfrom tcp 127.0.0.1:48802->127.0.0.1:7172: websocket: close 1006 (abnormal closure): unexpected EOF]
[2026-05-28 18:13:54.551] [Bridge] [[game] Closed connection for client: 192.168.45.210]


I currently have “anti-idle” enabled on 3 clients (using OTCv8-OTA). I suspect some default Cloudflare timeout might be causing the disconnections. I’ll keep monitoring it and will post again as soon as I have more information.

In my test environment, I’m not using HAProxy, since I already have Real IP injection: ENABLED (PROXYv2).

I’m using a single VPS to run everything (OVH).
did you make it to work properly without ping spikes?
 
made some changes 100% vibe coded tho ;p but made great changes for me, maybe can be good for you also who knows

New features:
- PROXY v2 support — upgraded the tunnel to send HAProxy PROXY v2 headers with the real player IP, and added parsing
in TFS. IP banning now works correctly through the tunnel instead of always seeing 127.0.0.1
- Session resumption — when Cloudflare rotates edge servers the connection drops. Added a token system: TFS generates
a session token on login and sends it to the client, if the connection drops the client silently reconnects using the
token instead of full re-authentication. Players never notice <-- im testing and bugging still much work on this
- Multi-port fallback — client automatically cycles through 3 ports on reconnect failure instead of
just giving up
if any1 need tips or need codes, im glad to help
 
Last edited:
Okay, after many hours of testing and banging my head against the wall, here’s a summary of the changes that made the setup significantly more stable and reliable for me.

-- Client side:** OTClient (OTCv8) already has a WebSocket transport in its network layer. Point it at your domain over a standard port. The game protocol rides inside the WS frames
-- Server side:** a tiny bridge. I used Go with gorilla/websocket: accept the WS connection, dial a plain TCP socket to TFS, copy bytes both directions. A few hundred lines, one binary, autostart on boot.
-- Cloudflare:** point the game subdomain at the VPS, turn the cloud orange, done. WS proxying is on.
-- Keep the real client IP:** the bridge speaks PROXY protocol v2 to TFS (TFS has proxyProtocol = true), using Cloudflare's CF-Connecting-IP header. So IP bans, logging and anti-multi-client still see the real player IP, not Cloudflare's.

The part nobody tells you about is making it stable — that's where I lost the most time, so here are the gotchas for me :D

## The gotchas that actually matter

These four are the difference between "it connects" and "it's kinda solid i think with ARGO from cloudflare it could be better"

### 1. Force the client to IPv4

Cloudflare always publishes an AAAA (IPv6) record now, and you cannot turn that off zone-side anymore. If your client's resolver races IPv6 vs IPv4, you get reconnect storms — connections flapping. Force the client's resolver to IPv4-only. This was the single biggest stability win. (And no, "Pseudo IPv4" does not help — it only rewrites an IP header, it doesn't change the transport.)

### 2. Raise TFS's connection timeout

TFS closes a socket if it sees no data for ~30 seconds (the default read/write timeout). On raw TCP that's fine. But the Cloudflare WS path buffers/stalls traffic in short bursts, especially when a player is standing still and only sparse keepalive pings flow. That transient gap can cross 30s → TFS kills a connection that is actually alive → drop → reconnect.

Raise the timeout to 60s. I matched it to pzLocked (60s) on purpose, so a combat disconnect stays in-world exactly as long as the PZ rule already allows — it adds no extra combat-log window. Truly dead sockets still get reaped by TCP keepalive. This killed the idle drops completely.

### 3. Run the bridge at NORMAL OS priority

I tried being clever and set the Go bridge to High process priority "for performance." It made latency worse — a busy Go process at High priority fights the OS network scheduling and adds jitter. Leave the bridge at Normal. (Your TFS process can be High, that's fine — it's the bridge specifically that must not be.)

### 4. One socket, one standard port

Cloudflare only reliably proxies WebSocket on its standard ports. Don't let the client rotate through extra game ports "for fallback" — the non-standard ones fail the vast majority of the time and just cause confusing dropouts. Pin the game to one standard port and keep a single socket.

---

## Bonus 1: seamless (silent) reconnect

Once drops were rare, I added an RSA-secured token reattach: the server hands the client a single-use, encrypted token; if the connection ever breaks, the client silently reconnects and reattaches to its existing in-world character — a brief freeze or rly small dropout, then you're back, instead of being kicked to the character list. Most servers don't have this at all. The important detail is handling the race where the reconnect arrives before the server has cleaned up the old connection — kick the stale one, retry briefly, then attach.

-- Bonus 2: stay listed on otservlist without exposing your IP
Server lists (otservlist etc.) ping your login server directly — which would expose your origin. Solution: a separate, grey-clouded subdomain pointing at a cheap external proxy (a small VPS running HAProxy with send-proxy-v2), which forwards to your real login port. Firewall your origin so it only accepts that proxy's IP. Result: you show up online on the lists, players still connect through Cloudflare for the game, and your real IP stays hidden the whole time.

## Security: how it's actually locked down

This isn't just "hide the IP and hope." It's layered — defense in depth:

1. Hidden origin + Cloudflare DDoS. Players and server-lists only ever see Cloudflare IPs. Volumetric (L3/L4) and application (L7) attacks hit Cloudflare's network, not your box.
2. Origin firewall locked to Cloudflare. Even if someone does discover your real IP, the game ports only accept connections from Cloudflare's published IP ranges — everything else is dropped. In a 5.5-hour test the firewall blocked 240 direct scanner probes, zero of them from Cloudflare, while 100% of legit traffic went through. [[Leaking the IP is no longer game-over]]!!
3. Real player IP preserved (PROXY protocol v2). A naive proxy makes every player look like they come from Cloudflare — which silently breaks IP bans, rate limits and anti-multiclient. PROXYv2 forwards the real CF-Connecting-IP to TFS, so all your IP-based moderation keeps working exactly as it did on raw TCP.
4. Encrypted, single-use reconnect token (RSA). The seamless-reconnect token is RSA-secured and consumed on first use, and the channel rides the standard OT XTEA encryption. A token can't be sniffed off the wire and replayed to hijack someone's session.
5. Rate limiting at the bridge. The bridge enforces a minimum gap between (re)connections per source, so connection-flood / reconnect-spam is throttled before it ever reaches TFS.
6. WSS / TLS in transit. The player↔Cloudflare leg is HTTPS / WebSocket-Secure, so the tunnel is encrypted on the public internet on top of the game's own encryption.
 
Back
Top