Two phones in different places need something in between to pass moves. Four designs were considered: WebRTC peer-to-peer, a relay on a rented server (VPS), AWS serverless, and Cloudflare Durable Objects. This page compares them. Architecture explains the one that won.
Every design below has to meet the same bar. These come from what Farboard already promises players (see the home page and Architecture):
Phone A creates a game and shares an invite link or QR code. When Phone B opens it, B loads the page and initiates its own connection to join the same game. A also initiates its own connection. The invite gives B the details it needs to join.
Read the arrows: each arrow points from the phone opening a connection (or sending a setup request) to the service receiving it. The Cloudflare arrow from Worker to Room shows routing. Page downloads and server responses are omitted.
WebRTC: signalling carries offers, answers and candidate addresses in both directions. STUN helps discover a public address; TURN supplies a relay path if a direct path cannot be used (orange dashed arrows). ICE tests the candidate paths between peers; those checks are omitted here. See WebRTC connectivity.
For the three WebSocket designs, each phone opens a connection that can then carry messages in both directions. Farboard's full joining sequence shows the requests and replies over time.
Both players are now connected. Phone A plays e2 → e4 and sends the encrypted move.
The arrows below follow that message to Phone B over the connections already established.
When B plays, the message travels back the other way.
Read the arrows: green shows the move being delivered; orange dashed arrows show the alternative TURN route. In AWS, grey arrows show the lookup and delivery calls needed to forward it.
AWS: API Gateway invokes Lambda. Lambda looks up B’s connection ID in DynamoDB,
then calls PostToConnection on API Gateway to deliver the move over B’s existing socket.
AWS WebSocket API overview.
Cloudflare: the Worker routes the initial upgrade; subsequent messages are handled by the Room over its accepted sockets. See Cloudflare WebSockets. The Cloudflare lane has the same shape as the VPS lane: one thing in the middle holds both players. The difference is that Cloudflare runs it, one per game, and puts it to sleep between moves.
The phones talk to each other directly over a WebRTC data channel. A signalling server introduces them first, and STUN helps each phone find its public address. It sounds as if it needs no server, and the data channel is encrypted by default.
Why not: direct connections fail on some mobile and office networks, which then need a TURN relay. You still run a signalling server as well, so it takes two servers to avoid one. More below.
A small rented server runs a WebSocket process. Both players connect to it, and it forwards each move to the other socket. The code is short and the model is the one most web developers already know.
Why not: a monthly fee whether anyone plays or not. You also own the operating system updates, TLS certificates, restarts and monitoring. It sits in one region, and a one-button copy for other people is much harder.
API Gateway holds the WebSockets. Each message starts a Lambda, which looks up the other player's
connection in DynamoDB and sends the move with PostToConnection. IAM roles and
infrastructure as code tie it together.
Why not: about $1 a month at 100 games a day, and it keeps billing if usage spikes. Budget alerts warn you but do not stop spending. Cost breakdown.
A Worker routes each game to its own Durable Object, called a Room. The Room holds both WebSockets
and forwards moves with one send(). The code is shaped like a VPS relay, but there is
no server to run.
Why: $0 on the free plan. 100 games a day uses about 2% of the allowance, and at the limit it pauses instead of billing. It hibernates between moves, and one button deploys a copy. The trade-off is that it's specific to Cloudflare.
| WebRTC | VPS relay | AWS serverless | Cloudflare (today) | |
|---|---|---|---|---|
| Servers or services to set up | Signalling + TURN | One server | API Gateway, Lambda, DynamoDB, IAM | One Worker, one class |
| Works on every network | Only with TURN Symmetric NAT and UDP-blocking firewalls stop direct links | Yes Plain HTTPS WebSocket | Yes Plain HTTPS WebSocket | Yes Plain HTTPS WebSocket |
| After a phone sleeps | The connection has to be negotiated again through the signalling server | Reconnect to the same server | Reconnect; a new connection id goes into DynamoDB | Reconnect to the same Room by name |
| Who sees your IP address | Your opponent, unless every packet goes through TURN | Only the server | Only AWS | Only the relay |
| Running it | Two services, and debugging connections that work on one network but not another | OS updates, TLS certificates, restarts, monitoring | Several consoles, IAM policies, budgets | Nothing to patch; logs and metrics in one dashboard |
| Cost at 100 games a day | Small. Chess uses little TURN bandwidth, but the signalling and TURN services still have to be hosted somewhere | A fixed monthly fee even when nobody plays | ≈ $1 a month | $0 |
| If usage spikes | Depends where the two servers run | Slows or drops connections; the bill stays fixed | Keeps billing | Online play pauses until 00:00 UTC; never billed |
| A copy of your own | Hard: two services to deploy | Hard: rent and set up a server | Hard: an AWS account and a stack to deploy | One Deploy to Cloudflare button |
| Lock-in | Low: an open standard | None | High: three AWS-only services | Medium: Durable Objects exist only on Cloudflare, but the relay is one small file (relay/src/room.js) |
AWS and Cloudflare figures come from Architecture, which also covers Vercel.
WebRTC lets two browsers connect directly, but only if their networks allow it. Each phone asks a STUN server what its public address looks like, then both try to punch through their routers to reach each other (a process called ICE). That works on most home networks. It often fails when:
Those connections need a TURN server that relays every packet. By common estimates that's roughly 10–25% of real-world sessions (Fora Soft, BlogGeek.me). So a WebRTC design is really three parts: a signalling server, a TURN relay, and the direct link when it works.
For a video call, avoiding a relay saves real bandwidth. Chess moves are a few hundred bytes each, so that saving doesn't matter. WebRTC would add a second way of connecting to test and debug, and it would show each player's IP address to the other. A relay was needed either way, so the simplest design was a relay for everyone.
None of the ideas behind the Cloudflare design are new. What changed is how little they cost to run.
Each piece of state lives in its own small process, and only that process can change it. A Durable Object is exactly this: one Room per game, the only code that can change that game's seats. That rules out locks and database race conditions.
It's what you would write on a VPS: one process holds both connections and forwards moves. A Room is that process, one per game, running on Cloudflare's network instead of on a machine you rent.
Workers run in V8 isolates, the same sandbox a browser gives each tab. One starts in milliseconds and uses little memory, so a single machine can hold thousands of them.
A Room sleeps between moves while its WebSockets stay open at the edge. A chess game spends almost all of its time waiting for someone to think, so it costs almost nothing.
AWS built serverless by combining separate services, each billed on its own. Cloudflare built a smaller set of building blocks around one cheap unit, the isolate, and gave state a home right next to it. Cloudflare also builds its Agents SDK on the same building block, one Durable Object per AI agent. That suggests the design will be supported for a long time.
I've run VPS servers for 20 years, so a VPS relay was my natural choice. AWS serverless felt like it needed a certification just to understand the moving parts. I didn't know Cloudflare's design existed until an AI coding assistant suggested it, together with the VPS and WebRTC options on this page.
The AI helped in three ways. It showed me the option existed. It built a working version quickly enough that trying it was worth the time. And it explained Durable Objects well enough that I could judge the design myself. Without that, I'd have stayed with the VPS, because testing and designing something unfamiliar would have cost more time than it could save.
The choice was still mine. I made it by comparing working versions instead of guessing.
Old ideas, new economics tells the longer story.
WebRTC connection figures are estimates from Fora Soft and BlogGeek.me; see also MDN: Introduction to WebRTC protocols. AWS and Cloudflare costs are from Architecture, checked on 26 September 2026.