PushFlo
Back to Blog
4 min readBy Marek

Why Serverless Functions Can't Hold WebSocket Connections (And How to Fix It)

A technical deep-dive into why WebSockets are incompatible with serverless architecture, and the practical solutions available for adding real-time features to your apps.

serverlesswebsocketarchitecturevercelaws-lambdaeducation

"Why can't I just open a WebSocket in my API route?"

This is one of the most common questions from developers new to serverless. The answer reveals a fundamental architectural incompatibility — and understanding it will help you design better real-time systems.

The Promise of WebSockets

Traditional HTTP works like this: Every interaction requires a new request. If you want to know when something changes, you have to keep asking.

WebSockets flip this model: One connection, bidirectional communication, server can push whenever it wants. Perfect for chat, notifications, live dashboards, multiplayer games.

How Serverless Functions Work

Serverless functions (AWS Lambda, Vercel Functions, Cloudflare Workers) work fundamentally differently from traditional servers.

Traditional Server

The server is always running. It keeps connections open in memory. When you want to send a message to User #2, you find their connection object and call connection.send().

Serverless Function

Key characteristics:

  1. Ephemeral — Functions don't run continuously
  2. Stateless — No memory between invocations
  3. Request/Response — One input, one output, done
  4. Timeout limits — Can't run forever (10s-300s depending on platform)

The Incompatibility

WebSockets need:

  • Persistent connections (hours or days)
  • State (tracking who's connected)
  • Ability to send messages at any time
  • No timeout

Serverless provides:

  • Short-lived execution (seconds to minutes)
  • Stateless (no persistent memory)
  • Request/response model only
  • Hard timeout limits

It's not that serverless is bad — it's that the two models are architecturally incompatible.

Solution 1: Separate the Concerns

The modern solution is to separate WebSocket handling from your application logic.

  • Clients connect to the WebSocket service (not your serverless functions)
  • WebSocket service holds the connections
  • Your serverless functions publish messages via REST API
  • WebSocket service delivers to connected clients

Your functions stay stateless. WebSocket connections are someone else's problem — see how this works in practice with WebSockets for Vercel or any serverless platform.

Code Example

// Client: Connect to PushFlo, not to your server
import { PushFloClient } from '@pushflodev/sdk';

const client = new PushFloClient({ publishKey: 'YOUR_PUBLISH_KEY' });
client.connect();
client.subscribe('notifications', {
  onMessage: (data) => {
    showNotification(data);
  },
});
// Server: Your serverless function publishes via REST
export async function POST(request: Request) {
  // Do your business logic
  const result = await processOrder(request);

  // Notify user via WebSocket service
  await fetch('https://api.pushflo.dev/api/v1/publish', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer YOUR_KEY' },
    body: JSON.stringify({
      channel: 'notifications',
      data: { message: 'Order complete!' }
    })
  });

  return Response.json(result);
}

Solution 2: Edge Functions + Streaming (Limited)

Some platforms offer edge functions with streaming support, like Server-Sent Events. Good for streaming AI responses, not for real-time multiplayer.

Solution 3: Cloudflare Durable Objects (Platform-Specific)

If you're all-in on Cloudflare, Durable Objects can hold WebSocket connections. Powerful but vendor lock-in.

Comparison of Solutions

SolutionComplexityCostVendor Lock-inBest For
Managed Service (PushFlo)Low$LowMost projects
Edge + SSEMedium$MediumStreaming responses
Durable ObjectsHigh$$High (Cloudflare)Cloudflare-native
HybridHigh$$$LowCustom requirements

The Mental Model

Think of it this way:

Serverless functions are like phone calls — you connect, talk, hang up.

WebSockets are like leaving the phone line open all day.

You can't leave a phone call open on a system designed for quick calls.

The solution isn't to fight the architecture. It's to use a service designed for keeping lines open, and have your serverless functions communicate with it.

Conclusion

Serverless functions can't hold WebSocket connections because:

  1. They're ephemeral (functions terminate)
  2. They're stateless (no connection tracking)
  3. They have timeouts (connections would die)
  4. The request/response model doesn't fit bidirectional communication

The fix is architectural separation:

  • Let a WebSocket service hold connections
  • Your serverless functions publish messages via REST
  • Users get real-time updates
  • You keep your serverless benefits

This isn't a workaround — it's the correct architecture for serverless + real-time.


Ready to add real-time to your serverless app? Get started with PushFlo — we hold the WebSocket connections so your functions don't have to.

Add real-time to your serverless stack

PushFlo manages persistent WebSocket connections so your functions stay stateless.