Back to Blog
Tutorial10 min read2026-07-04

How to Deploy a WebSocket Server in the Cloud

Deploy a production WebSocket server: ingress and upgrade handling, heartbeats and idle timeouts, horizontal scaling with a pub/sub backplane, graceful shutdown, and connection limits.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

WebSocket servers behave differently from request/response APIs in ways that matter for deployment. Connections are long-lived, they consume a slot for their entire lifetime, idle connections can be silently dropped by intermediaries, and broadcasting across multiple instances requires shared state. This guide is framework-agnostic (examples in Node with the ws library) and focuses on the production concerns that bite people.

A baseline server

import { WebSocketServer } from 'ws';
const port = process.env.PORT || 8080;
const wss = new WebSocketServer({ port, host: '0.0.0.0' });

wss.on('connection', (ws) => {
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });
  ws.on('message', (data) => {
    // broadcast to everyone
    wss.clients.forEach((c) => {
      if (c.readyState === c.OPEN) c.send(data);
    });
  });
});

Binding to 0.0.0.0 is required in a container. That broadcast loop works for a single instance, scaling it correctly is the harder part, covered below.

Heartbeats and idle timeouts

The most common production bug: connections that look open but are actually dead. Networks, proxies, and load balancers drop idle TCP connections, sometimes silently. The fix is a heartbeat, periodic ping/pong to detect and clean up dead connections:

const interval = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('close', () => clearInterval(interval));

Also be aware of ingress idle timeouts: a connection with no traffic for too long may be cut by the proxy. Heartbeats keep the connection active and detect breakage from both ends.

Ingress and the upgrade handshake

A WebSocket connection starts as an HTTP request with an Upgrade: websocket header; the server responds 101 Switching Protocols. Your ingress must allow and forward that upgrade. Kong ingress supports WebSocket upgrades, so connections pass through transparently with TLS terminated at the edge (wss:// from the client). Confirm your client uses wss:// against the HTTPS domain so the connection is encrypted end to edge.

Scaling horizontally: the backplane problem

Here's the core distributed-systems challenge. If client A connects to instance 1 and client B connects to instance 2, a message from A won't reach B, because instance 1 has no idea instance 2's clients exist. The in-memory wss.clients set is per-instance.

The solution is a pub/sub backplane, typically Redis. Each instance subscribes to a channel; when it receives a message to broadcast, it publishes to Redis, and every instance (including itself) receives it and fans out to its own local clients:

import { createClient } from 'redis';
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await pub.connect(); await sub.connect();

await sub.subscribe('broadcast', (msg) => {
  wss.clients.forEach((c) => c.readyState === c.OPEN && c.send(msg));
});

// on incoming message from a client:
function broadcast(msg) { pub.publish('broadcast', msg); }

This pattern is what makes horizontal scaling actually work for WebSocket servers.

Graceful shutdown

During a deploy, close connections cleanly so clients can reconnect to the new instance rather than seeing abrupt resets:

process.on('SIGTERM', () => {
  wss.clients.forEach((c) => c.close(1001, 'Server shutting down'));
  wss.close(() => process.exit(0));
});

Close code 1001 ("going away") tells clients this is expected and they should reconnect.

Deploying on PandaStack

  1. 1Provision a managed Redis instance for the pub/sub backplane. Reference via REDIS_URL.
  2. 2Connect your repo as a container app. Build runs in an ephemeral Job pod with rootless BuildKit and deploys via Helm behind Kong ingress (WebSocket upgrades supported), with automatic SSL so clients use wss://.
  3. 3Set REDIS_URL to activate the backplane before scaling to more than one replica.
  4. 4Tail live logs to watch connections, pings, and the 101 handshakes.
ConcernProduction answer
Dead connections30s ping/pong heartbeat
Idle timeoutHeartbeats keep alive
Multi-instance broadcastRedis pub/sub backplane
ShutdownClose with code 1001
Transport securitywss:// via auto SSL

Scale-to-zero caution

Scale-to-zero and persistent connections conflict: scaling to zero drops every connection. For a server with continuously connected clients, keep a warm instance on a paid tier. The free tier suits low-traffic or intermittently used realtime features where clients reconnect gracefully (your client should implement reconnect-with-backoff regardless).

Connection limits and memory

Each connection consumes memory and a file descriptor. At high concurrency, choose a memory-optimized tier and track memory in the server-side metrics. If you're holding per-connection state (rooms, user data), that scales with connection count, so plan capacity accordingly.

Common pitfalls

  • No heartbeat, dead connections accumulate and broadcasts hit zombies.
  • No backplane, broadcasts don't cross instances after you scale.
  • ws:// instead of wss://, unencrypted and often blocked by browsers on HTTPS pages.
  • Scale-to-zero on a persistent-connection server, mass disconnects on idle.

References

  • WebSocket protocol (RFC 6455): https://datatracker.ietf.org/doc/html/rfc6455
  • ws (Node WebSocket library): https://github.com/websockets/ws
  • Redis pub/sub: https://redis.io/docs/latest/develop/interact/pubsub/
  • WebSocket close codes (IANA): https://www.iana.org/assignments/websocket/websocket.xml
  • MDN WebSocket API: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket

A production WebSocket server lives or dies by heartbeats, a pub/sub backplane for scaling, and graceful shutdown. Deploy yours with managed Redis and automatic wss:// on PandaStack's free tier: https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also