WebSocket Connections
This guide shows you how to work with WebSocket servers running in your sandboxes.
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.
Create the echo server:
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:
FROM docker.io/cloudflare/sandbox:0.3.3
# Copy echo server into the containerCOPY echo-server.ts /workspace/echo-server.ts
# Create custom startup scriptCOPY startup.sh /container-server/startup.shRUN chmod +x /container-server/startup.shCreate startup script:
#!/bin/bash# Start your WebSocket server in the backgroundbun /workspace/echo-server.ts &# Start SDK's control plane (needed for the SDK to work)exec bun dist/index.jsConnect from your Worker:
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"); },};import { getSandbox } from '@cloudflare/sandbox';
export { Sandbox } from "@cloudflare/sandbox";
export default { async fetch(request: Request, env: Env): Promise<Response> { 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:
const ws = new WebSocket('wss://your-worker.com');ws.onmessage = (event) => console.log(event.data);ws.send('Hello!'); // Receives: "Echo: Hello!"Get a public URL for your WebSocket server:
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"); },};import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';
export default { async fetch(request: Request, env: Env): Promise<Response> { 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:
// Get the preview URLconst response = await fetch('https://your-worker.com/ws-url');const { url } = await response.json();
// Connectconst ws = new WebSocket(url);ws.onmessage = (event) => console.log(event.data);ws.send('Hello!'); // Receives: "Echo: Hello!"Your Worker can connect to a WebSocket service to get real-time data, even when the incoming request isn't a WebSocket:
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"); },};export default { async fetch(request: Request, env: Env): Promise<Response> { 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.
Verify request has WebSocket headers:
console.log(request.headers.get("Upgrade")); // 'websocket'console.log(request.headers.get("Connection")); // 'Upgrade'console.log(request.headers.get('Upgrade')); // 'websocket'console.log(request.headers.get('Connection')); // 'Upgrade'Expose ports in Dockerfile for wrangler dev:
FROM docker.io/cloudflare/sandbox:0.3.3
COPY echo-server.ts /workspace/echo-server.tsCOPY startup.sh /container-server/startup.shRUN chmod +x /container-server/startup.sh
# Required for local developmentEXPOSE 8080- Ports API reference - Complete API documentation
- Preview URLs concept - How preview URLs work
- Background processes guide - Managing long-running services
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-