---
title: WebSocket Connections · Cloudflare Sandbox SDK docs
description: Connect to WebSocket servers running in sandboxes.
lastUpdated: 2025-11-08T10:22:38.000Z
chatbotDeprioritize: false
source_url:
  html: https://developers.cloudflare.com/sandbox/guides/websocket-connections/
  md: https://developers.cloudflare.com/sandbox/guides/websocket-connections/index.md
---

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:**

```typescript
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:**

```bash
#!/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

  ```js
  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");
    },
  };
  ```

* TypeScript

  ```ts
  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:**

```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

  ```js
  import { getSandbox, proxyToSandbox } from "@cloudflare/sandbox";


  export { Sandbox } from "@cloudflare/sandbox";


  export default {
    async fetch(request, env) {
      // Auto-route all requests via proxyToSandbox first
      const proxyResponse = await proxyToSandbox(request, env);
      if (proxyResponse) return proxyResponse;


      // Extract hostname from request
      const { hostname } = new URL(request.url);
      const sandbox = getSandbox(env.Sandbox, "echo-service");


      // Expose the port to get preview URL
      const { exposedAt } = await sandbox.exposePort(8080, { hostname });


      // Return URL to clients
      if (request.url.includes("/ws-url")) {
        return Response.json({ url: exposedAt.replace("https", "wss") });
      }


      return new Response("Not found", { status: 404 });
    },
  };
  ```

* TypeScript

  ```ts
  import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';


  export { Sandbox } from '@cloudflare/sandbox';


  export default {
    async fetch(request: Request, env: Env): Promise<Response> {
      // Auto-route all requests via proxyToSandbox first
      const proxyResponse = await proxyToSandbox(request, env);
      if (proxyResponse) return proxyResponse;


      // Extract hostname from request
      const { hostname } = new URL(request.url);
      const sandbox = getSandbox(env.Sandbox, 'echo-service');


      // Expose the port to get preview URL
      const { exposedAt } = await sandbox.exposePort(8080, { hostname });


      // Return URL to clients
      if (request.url.includes('/ws-url')) {
        return Response.json({ url: exposedAt.replace('https', 'wss') });
      }


      return new Response('Not found', { status: 404 });
  }
  };
  ```

**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

  ```js
  import { getSandbox } from "@cloudflare/sandbox";


  export { Sandbox } from "@cloudflare/sandbox";


  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");
    },
  };
  ```

* TypeScript

  ```ts
  import { getSandbox } from '@cloudflare/sandbox';


  export { Sandbox } from '@cloudflare/sandbox';


  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.

## Troubleshooting

### Upgrade failed

Verify request has WebSocket headers:

* JavaScript

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

* TypeScript

  ```ts
  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
```

Note

Port exposure in Dockerfile is only required for local development. In production, all ports are automatically accessible.

## Related resources

* [Ports API reference](https://developers.cloudflare.com/sandbox/api/ports/) - Complete API documentation
* [Preview URLs concept](https://developers.cloudflare.com/sandbox/concepts/preview-urls/) - How preview URLs work
* [Background processes guide](https://developers.cloudflare.com/sandbox/guides/background-processes/) - Managing long-running services
