Skip to content
Cloudflare Docs

WebSocket Connections

This guide shows you how to work with WebSocket servers running in your sandboxes.

Choose your approach

Expose via preview URL - Get a public URL for external clients to connect to. Best for public chat rooms, multiplayer games, or real-time dashboards.

Connect with wsConnect() - Your Worker establishes the WebSocket connection. Best for custom routing logic, authentication gates, or when your Worker needs real-time data from sandbox services.

Connect to WebSocket echo server

Create the echo server:

echo-server.ts
Bun.serve({
port: 8080,
hostname: "0.0.0.0",
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("WebSocket echo server");
},
websocket: {
message(ws, message) {
ws.send(`Echo: ${message}`);
},
open(ws) {
console.log("Client connected");
},
close(ws) {
console.log("Client disconnected");
},
},
});
console.log("WebSocket server listening on port 8080");

Extend the Dockerfile:

Dockerfile
FROM docker.io/cloudflare/sandbox:0.3.3
# Copy echo server into the container
COPY echo-server.ts /workspace/echo-server.ts
# Create custom startup script
COPY startup.sh /container-server/startup.sh
RUN chmod +x /container-server/startup.sh

Create startup script:

startup.sh
#!/bin/bash
# Start your WebSocket server in the background
bun /workspace/echo-server.ts &
# Start SDK's control plane (needed for the SDK to work)
exec bun dist/index.js

Connect from your Worker:

JavaScript
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") {
const sandbox = getSandbox(env.Sandbox, "echo-service");
return await sandbox.wsConnect(request, 8080);
}
return new Response("WebSocket endpoint");
},
};

Client connects:

JavaScript
const ws = new WebSocket('wss://your-worker.com');
ws.onmessage = (event) => console.log(event.data);
ws.send('Hello!'); // Receives: "Echo: Hello!"

Expose WebSocket service via preview URL

Get a public URL for your WebSocket server:

JavaScript
import { getSandbox, proxyToSandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
const sandbox = getSandbox(env.Sandbox, "echo-service");
// Expose the port to get preview URL
const { exposedAt } = await sandbox.exposePort(8080);
// Return URL to clients
if (request.url.includes("/ws-url")) {
return Response.json({ url: exposedAt.replace("https", "wss") });
}
// Auto-route all requests via proxyToSandbox
return proxyToSandbox(request, env.Sandbox, "echo-service");
},
};

Client connects to preview URL:

JavaScript
// Get the preview URL
const response = await fetch('https://your-worker.com/ws-url');
const { url } = await response.json();
// Connect
const ws = new WebSocket(url);
ws.onmessage = (event) => console.log(event.data);
ws.send('Hello!'); // Receives: "Echo: Hello!"

Connect from Worker to get real-time data

Your Worker can connect to a WebSocket service to get real-time data, even when the incoming request isn't a WebSocket:

JavaScript
export default {
async fetch(request, env) {
const sandbox = getSandbox(env.Sandbox, "data-processor");
// Incoming HTTP request needs real-time data from sandbox
const wsRequest = new Request("ws://internal", {
headers: {
Upgrade: "websocket",
Connection: "Upgrade",
},
});
// Connect to WebSocket service in sandbox
const wsResponse = await sandbox.wsConnect(wsRequest, 8080);
// Process WebSocket stream and return HTTP response
// (Implementation depends on your needs)
return new Response("Processed real-time data");
},
};

This pattern is useful when you need streaming data from sandbox services but want to return HTTP responses to clients.

Troubleshooting

Upgrade failed

Verify request has WebSocket headers:

JavaScript
console.log(request.headers.get("Upgrade")); // 'websocket'
console.log(request.headers.get("Connection")); // 'Upgrade'

Local development

Expose ports in Dockerfile for wrangler dev:

Dockerfile
FROM docker.io/cloudflare/sandbox:0.3.3
COPY echo-server.ts /workspace/echo-server.ts
COPY startup.sh /container-server/startup.sh
RUN chmod +x /container-server/startup.sh
# Required for local development
EXPOSE 8080