Real-time breaks the serverless assumptions
Request/response serverless is a beautiful model — until your app needs a connection that *stays open*. Chat, live dashboards, collaborative editors, multiplayer, presence, notifications: all of these rely on WebSockets or Server-Sent Events, and those need a server that holds connections for minutes or hours.
That single requirement disqualifies a surprising number of platforms or forces you onto a separate "real-time add-on." This post is about hosting the real-time server itself.
What real-time hosting actually requires
- Long-lived connections. WebSockets/SSE that don't get killed at 30–60s by an aggressive proxy.
- Sticky sessions or shared state. If you scale to multiple instances, a client's connection lives on one instance. You need either sticky routing or a shared backplane (Redis pub/sub).
- Graceful deploys. Rolling a new version shouldn't drop every connection at once with no reconnect strategy.
- A Redis (or similar) backplane. For fan-out across instances.
- Low-latency, multi-region option if your users are global.
The horizontal-scaling problem (read this first)
The number-one real-time mistake: assuming a single in-memory connection map works when you have more than one instance. User A connects to instance 1, user B to instance 2; A's message never reaches B. The fix is a backplane — typically Redis pub/sub — so any instance can broadcast to connections held by any other instance.
// socket.io with a Redis adapter for multi-instance fan-out
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);
io.adapter(createAdapter(pub, sub));If you can't run a backplane, the alternative is sticky sessions (route a client to the same instance every time) — but sticky sessions hurt load balancing and still break on deploys.
The options
Managed real-time SaaS (Pusher, Ably, etc.)
You don't host the socket layer at all — you publish to their API and they handle fan-out, presence, and scale. Excellent reliability and global edge. Trade-offs: per-message/connection pricing, vendor lock-in to their SDK, and your business logic still lives on a server you host elsewhere. Great when real-time is a feature, not the whole app.
Self-managed (VPS / k8s)
Run your Node/Go/Elixir socket server yourself, with Redis for the backplane and nginx/HAProxy tuned for WebSockets. Full control, full responsibility for tuning timeouts, sticky routing, and scaling.
Managed container PaaS
Run your own WebSocket server as a container with a managed Redis backplane. You own the app logic; the platform handles build, deploy, TLS, and the proxy. The critical question: does the ingress support WebSockets without short timeouts?
PandaStack
PandaStack runs real-time servers as container apps behind Kong ingress, which supports WebSocket upgrades. Pair it with managed Redis as your pub/sub backplane (connection string injected), get automatic SSL on custom domains, and multi-region GKE for latency. Honest caveat: free-tier apps scale to zero on preemptible nodes — wrong choice for always-connected production sockets, so run a warm paid instance for real-time workloads.
Comparison
| Option | Holds connections | Backplane | Multi-region | Lock-in | Best for |
|---|---|---|---|---|---|
| Pusher/Ably (SaaS) | Yes (theirs) | N/A | Yes | High (SDK) | Real-time as a feature |
| Self-managed | Yes | DIY Redis | DIY | None | Full control |
| Managed PaaS | Yes | Managed Redis | Varies | Low | Owning your socket server |
| PandaStack | Yes (Kong WS) | Managed Redis | Multi-region GKE | Low | Self-hosted real-time + Redis |
Deploy checklist for self-hosted real-time
- Confirm WebSocket upgrades pass the proxy. Test with
wscatagainst the public URL, not just localhost. - Set heartbeats/pings below any proxy idle timeout to keep connections alive.
- Use a Redis adapter (socket.io) or your own pub/sub for fan-out.
- Implement client reconnect with backoff — deploys and network blips happen.
- Externalize session/presence state to Redis so it survives a single instance dying.
- Keep at least one warm instance if you can't tolerate cold starts.
Quick WebSocket smoke test:
npx wscat -c wss://your-app.example.com/socket
# you should connect and stay connected; if it drops at ~30-60s, the proxy timeout is too lowSSE as a lighter alternative
If you only need server-to-client streaming (live logs, notifications, progress bars), Server-Sent Events are simpler than WebSockets: plain HTTP, auto-reconnect built into the browser, and no upgrade handshake. They still need a host that won't buffer or cut the response — same long-connection requirement, fewer moving parts.
My recommendation
If real-time is a bolt-on feature, a managed SaaS like Ably or Pusher is the fastest path and offloads the hard scaling problems. If real-time *is* your product — and you want to own the logic and avoid per-message pricing — host your own socket server on a container platform with a managed Redis backplane and a WebSocket-friendly ingress. PandaStack fits that second case; just run a warm paid instance rather than free-tier scale-to-zero for production sockets.
References
- socket.io Redis adapter: https://socket.io/docs/v4/redis-adapter/
- MDN WebSockets API: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
- MDN Server-Sent Events: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
- Ably docs: https://ably.com/docs
- WebSocket protocol (RFC 6455): https://datatracker.ietf.org/doc/html/rfc6455
---
Building a chat, live dashboard, or collaborative app? PandaStack runs your WebSocket server with managed Redis fan-out and WebSocket-friendly ingress. Try the free tier at https://dashboard.pandastack.io