Running Long-Lived WebSockets in Node.js and Go Without Cloudflare Timeout Disconnections | Deployxa

WebSockets are essential for realtime apps but break on serverless due to timeout limits. Here is how to run long-lived WebSocket servers on Deployxa's persistent containers.

← Back to Dispatch Articles
Engineering Log

Running Long-Lived WebSockets in Node.js and Go Without Cloudflare Timeout Disconnections

WebSockets are essential for realtime apps but break on serverless due to timeout limits. Here is how to run long-lived WebSocket servers on Deployxa's persistent containers.

Running Long-Lived WebSockets Without Timeout Disconnections

WebSockets are the backbone of realtime web applications: chat, collaborative editing, live dashboards, streaming AI output, multiplayer games, and any feature where the server pushes data to the client without polling. But deploying a WebSocket server on serverless platforms is a nightmare, because serverless functions are designed for short-lived, request-response workloads, not long-lived, persistent connections. Cloudflare Workers, Vercel Functions, and AWS Lambda all impose strict timeout limits (often 10 to 30 seconds for the free tier), which means WebSocket connections get killed mid-stream. The result is broken realtime features, frustrated users, and a maintenance burden that should not exist. Here is why WebSockets break on serverless, and how Deployxa's persistent containers run long-lived WebSocket servers without any timeout issues.

The direct answer is that a WebSocket connection is a long-lived, persistent TCP connection between the client and the server. The server holds the connection open indefinitely, pushing data to the client as it becomes available. Serverless platforms, which are designed for short-lived function executions, cannot hold these connections open, because they enforce timeout limits that kill idle functions. A WebSocket server on a serverless platform either does not work at all (because the platform does not support WebSockets), or works with severe limitations (because connections get killed after the timeout period).

Why WebSockets Are Incompatible with Serverless

Three properties of WebSockets make them fundamentally incompatible with serverless:

1. Long-lived connections

A WebSocket connection is designed to stay open for the duration of the user's session, which can be minutes, hours, or even days. The server holds the connection in memory, ready to push data when it becomes available. Serverless platforms, which spin functions down when idle, cannot hold these connections open. The function gets killed after the timeout period, the connection is dropped, and the client has to reconnect.

2. Stateful sessions

WebSocket servers typically maintain session state in memory: the user's identity, their subscriptions, their cursor position in a collaborative editor, their chat history. This state lives in the process's memory and is assumed to persist for the duration of the connection. Serverless platforms, which spawn a new process for each request, cannot maintain this state, because each request might be handled by a different process with no memory of previous requests.

3. Server-initiated communication

WebSockets allow the server to push data to the client without the client requesting it. This is essential for realtime features like chat notifications, live updates, and streaming AI output. Serverless platforms, which are request-response oriented, cannot push data to the client without an incoming request, because there is no persistent connection to push over.

The Specific Serverless Limitations

The specific WebSocket limitations on major serverless platforms are worth enumerating, because vibe coders often discover them the hard way. Cloudflare Workers supports WebSockets via "Durable Objects" but limits each Durable Object to 30 seconds of inactivity before eviction, which means idle connections get killed. Vercel does not support WebSocket servers on its serverless functions at all; the official recommendation is to use a third-party service (Pusher, Ably) or to deploy the WebSocket server separately on a VPS. AWS Lambda supports WebSockets via API Gateway, but each connection costs $0.25 per million minutes (which adds up), and the integration is complex (you write Lambda handlers for $connect, $disconnect, and $default routes, and use DynamoDB for cross-connection state). Google Cloud Run does not support WebSockets natively (it supports HTTP/2 server push, which is not the same), and the recommended pattern is to deploy a separate WebSocket server on Compute Engine.

The pattern across all serverless platforms is the same: WebSockets are a second-class citizen, supported only via workarounds that add complexity, cost, and latency. Deployxa's persistent containers treat WebSockets as a first-class citizen, because they are long-lived processes that can hold TCP connections open indefinitely.

The Serverless Workarounds and Why They Are Painful

Serverless platforms offer workarounds for WebSockets, but they are painful:

1. External WebSocket services

You can use an external WebSocket service like Pusher, Ably, or AWS API Gateway WebSockets. These services handle the WebSocket connections, and your serverless function pushes messages to them via HTTP. This works, but it adds a dependency, a cost, and a layer of indirection. For a vibe coder, integrating an external WebSocket service is non-trivial, and the LLM often does not know how to do it correctly.

2. Polling instead of WebSockets

You can fall back to polling, where the client sends an HTTP request every few seconds to check for updates. This works, but it is inefficient (lots of empty requests), it adds latency (updates are delayed by the polling interval), and it does not scale (each client generates a request every few seconds, which can overwhelm the server).

3. Long polling

You can use long polling, where the client sends a request and the server holds it open until data is available, then responds. This is more efficient than regular polling, but it still has the timeout problem, because serverless functions cannot hold requests open indefinitely.

None of these workarounds are as good as a native WebSocket server on a persistent container. They add complexity, cost, and latency, and they often break in subtle ways that are hard to debug.

The Cost of Workarounds

The cost of workarounds adds up. Pusher's Sandbox tier is free but limited to 100 max connections and 200k messages/day. Pusher's Pro tier starts at $49/month for 500 max connections and 1M messages. Ably's free tier is 6M messages/month, and paid tiers start at $29/month for higher limits. AWS API Gateway WebSockets cost $0.25 per million minutes of connection time, plus $1.08 per million messages. For a chat app with 1000 concurrent users each connected for 8 hours/day, the monthly cost on Pusher Pro is $49 + overages (likely $200-400/month total), and on AWS API Gateway is roughly $36 (connection time) + $0.50 (messages) = $36.50/month. Compare this to Deployxa at $9/month flat for the WebSocket server, and the savings are significant.

How Persistent Containers Solve WebSocket Deployment

Persistent containers solve all three problems:

1. Long-lived connections

The container stays alive indefinitely, so WebSocket connections stay open as long as the client wants. There is no timeout, because the container does not enforce one. The only limit is the container's resources (memory, file descriptors), which you can scale up as needed.

2. Stateful sessions

The same container handles all of a user's requests (with sticky session routing if you scale to multiple containers). Session state, subscriptions, and cursor positions persist in memory across requests, which is exactly what WebSocket servers are designed for.

3. Server-initiated communication

The container can push data to the client at any time, because the WebSocket connection is persistent. There is no need for an incoming request to trigger a response. The server just sends data when it becomes available.

How Deployxa's Containers Handle WebSockets

Deployxa's containers are hardened Docker cgroups on AMD EPYC bare-metal hosts behind Cloudflare. The Cloudflare front-end terminates TLS and forwards WebSocket connections to the container via HTTP/1.1 with the Upgrade: websocket header. The container's WebSocket library (Node.js ws, Go gorilla/websocket, Python websockets, etc.) handles the upgrade and manages the connection.

There is no in-path timeout for WebSocket connections. Cloudflare's free plan includes unlimited WebSocket connection duration, and Deployxa's container does not enforce one. The only limit is the container's file descriptor limit (default 65536, configurable), which caps the number of concurrent WebSocket connections at roughly 60k per container. For higher concurrency, scale to multiple containers.

Step-by-Step: Deploying a Node.js WebSocket Server

Here is the exact workflow for a typical Node.js WebSocket server using the ws library.

Step 1: Create your WebSocket server

// server.js
const { WebSocketServer } = require('ws');

const wss = new WebSocketServer({ port: process.env.PORT || 8080 });

wss.on('connection', (ws) => {
  console.log('New client connected');
  
  ws.on('message', (message) => {
    console.log('Received:', message.toString());
    ws.send(`Echo: ${message}`);
  });
  
  ws.on('close', () => {
    console.log('Client disconnected');
  });
  
  // Send a heartbeat every 30 seconds
  setInterval(() => {
    ws.send(JSON.stringify({ type: 'heartbeat', time: Date.now() }));
  }, 30000);
});

console.log(`WebSocket server running on port ${process.env.PORT || 8080}`);

Step 2: Create package.json

{
  "name": "my-websocket-server",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "ws": "^8.18.0"
  }
}

Step 3: Push to GitHub and deploy to Deployxa

git init
git add .
git commit -m "websocket server"
git push origin main

Connect your repository to Deployxa. The ingestion service detects Node.js and configures the start command (npm start). The container runs your WebSocket server, and it is live within 60 seconds. No timeout, no disconnection.

Step 4: Test the connection

Open a WebSocket client (like wscat or a browser console) and connect to your Deployxa URL:

wscat -c wss://my-app.deployxa.app

Send a message, and you should receive an echo. The connection stays open indefinitely, and the heartbeat arrives every 30 seconds.

Common Pitfalls

Three pitfalls appear in WebSocket deployments on Deployxa. First, heartbeat gaps. Cloudflare closes idle WebSocket connections after 100 seconds of inactivity (this is a Cloudflare-level limit, not a Deployxa one). Send a heartbeat (ping or a small message) every 30 seconds to keep the connection alive. The example code above does this. Second, file descriptor limits. Each WebSocket connection consumes a file descriptor. The default limit is 65536 per container, which is enough for tens of thousands of concurrent connections. If you need more, raise the limit in the Deployxa dashboard under Settings > Resource Limits. Third, connection state on container restart. When a container restarts (e.g., during a deploy), all WebSocket connections drop. Clients must handle reconnection. Use a library like reconnecting-websocket on the client side, and design your server to be stateless across restarts (store session state in Redis, not in process memory).

Troubleshooting: Common WebSocket Errors

Below are common errors and their interpretations.

Error: WebSocket connection to 'wss://my-app.deployxa.app' failed: Error during WebSocket handshake: Unexpected response code: 502

The container is not running, or it is not listening on the assigned port. Check deployxa doctor and the container logs.

Error: Cloudflare: 1006 (Connection closed abnormally)

The connection was closed after 100 seconds of inactivity. Add a heartbeat to your server.

Error: EMFILE: too many open files

The file descriptor limit is exceeded. Either raise the limit, or scale to multiple containers.

Error: WebSocket is closed before the connection is established

The client is using ws:// instead of wss:// against a Cloudflare-fronted URL. Use wss://.

Step-by-Step: Deploying a Go WebSocket Server

For a Go WebSocket server, the workflow is similar. Here is a minimal example using the gorilla/websocket library:

// main.go
package main

import (
    "log"
    "net/http"
    "os"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Upgrade error:", err)
        return
    }
    defer conn.Close()
    
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }
        log.Printf("Received: %s", message)
        err = conn.WriteMessage(messageType, message)
        if err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    http.HandleFunc("/ws", handleWebSocket)
    log.Printf("WebSocket server running on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Push to GitHub, connect to Deployxa, and the Go WebSocket server is live within 60 seconds.

Go-Specific Notes

Go's goroutine model is well-suited to WebSocket servers, because each connection can be handled in its own goroutine with minimal overhead. A Go WebSocket server on Deployxa can handle tens of thousands of concurrent connections on a 512MB container, vs a few thousand for Node.js on the same container. For high-concurrency WebSocket workloads (chat apps with thousands of users, live dashboards with many subscribers), Go is the better choice.

The gorilla/websocket library is the most popular, but it has been in maintenance mode since 2022. The recommended alternative is nhooyr.io/websocket (now coder.com/websocket), which has a simpler API and better performance. Both work on Deployxa.

Realtime AI Streaming: A Key Use Case

One of the most important use cases for long-lived WebSockets in 2026 is AI streaming. When an LLM generates a long response, the user experience is dramatically better if the response streams token by token, rather than waiting for the full response. This requires a WebSocket (or Server-Sent Events) connection, because the server needs to push tokens to the client as they are generated.

On a serverless platform, this is painful, because the function timeout might kill the connection before the LLM finishes generating. On a persistent container, the connection stays open as long as needed, and the user sees the full streamed response. For AI apps that depend on streaming (chatbots, code generators, content writers), persistent containers are not optional; they are required.

A Concrete Streaming Example

Here is a minimal Node.js WebSocket server that streams an OpenAI response token by token:

const { WebSocketServer } = require('ws');
const OpenAI = require('openai');

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const wss = new WebSocketServer({ port: process.env.PORT || 8080 });

wss.on('connection', (ws) => {
  ws.on('message', async (message) => {
    const { prompt } = JSON.parse(message);
    const stream = await client.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: prompt }],
      stream: true,
    });
    for await (const chunk of stream) {
      const token = chunk.choices[0]?.delta?.content || '';
      if (token) ws.send(JSON.stringify({ type: 'token', token }));
    }
    ws.send(JSON.stringify({ type: 'done' }));
  });
});

On a serverless platform with a 30-second timeout, this breaks for any prompt that takes longer than 30 seconds to stream. On a Deployxa persistent container, it works regardless of response length.

The Pricing Reality: WebSockets on Persistent Containers

WebSocket servers on persistent containers are priced by provisioned resources, not by connection count or message count. This means the bill is predictable, regardless of how many WebSocket connections your app handles or how many messages it sends. For a realtime app with many active connections, this is usually cheaper than serverless, which often charges per connection or per message.

Deployxa's free tier includes 3 active apps with 512MB RAM, which is enough to run a small WebSocket server with hundreds of concurrent connections. The paid tier starts at $9 per month for 15 apps, with predictable pricing.

Cost Comparison: WebSocket Hosting

| Provider | Pricing model | Cost for 1000 concurrent connections |

|---|---|---|

| Deployxa Paid | Flat | $9/month |

| Pusher Pro | Per connection + per message | $49-200/month (overages for connections + messages) |

| Ably | Per connection + per message | $29-150/month (similar overage model) |

| AWS API Gateway WebSockets + Lambda | Per connection-minute + per message | $36-50/month (connection time) + Lambda costs |

| Fly.io | Per VM-hour | $5-20/month (1-2 shared VMs) |

| Render | Per instance-hour | $7-25/month (1 starter instance) |

For a vibe coder running a single WebSocket server, Deployxa Paid is competitive with the cheapest alternatives and significantly cheaper than managed WebSocket services (Pusher, Ably). For high-concurrency workloads, the flat pricing model means Deployxa's cost does not scale with connection count, which is a major advantage.

Conclusion: Give Your WebSockets a Persistent Home

WebSockets are essential for realtime web applications, but they are fundamentally incompatible with serverless platforms. The timeout limits, stateless execution, and request-response model of serverless all work against the long-lived, stateful, server-initiated nature of WebSocket connections. Deployxa's persistent containers give WebSocket servers the home they need: no timeouts, no disconnections, no workarounds.

Ready to deploy your WebSocket server? Drag your project to Deployxa Drop for an instant live preview, or install the CLI with npm i -g @deployxa/cli and deploy from your terminal. For more on persistent containers vs serverless, see Deployxa vs Vercel and explore our free developer tools to speed up your workflow.

Ready to deploy with Deployxa?

Deploy your apps globally with automatic SSL and AI diagnostics.

Start Free Now