Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

FastAPI WebSocket: Build a Connection That Survives Production

Sean

Platform Writer

Jul 22, 2026
11 min read

A FastAPI WebSocket endpoint must accept the handshake, authenticate the connection, handle disconnects, and bound every queue; the echo loop is only the first ten lines.

FastAPI WebSocket: Build a Connection That Survives Production

Long-lived connections change failure behavior. Deployments, proxies, slow clients, and multiple workers all become part of the protocol.

Table of contents

Build the smallest correct endpoint

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket('/ws')
async def socket(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            message = await websocket.receive_text()
            await websocket.send_json({'echo': message})
    except WebSocketDisconnect:
        pass

Accept before sending application messages, and catch disconnects so registry cleanup runs. Validate message shape and size before processing. A client controls when data arrives, so every receive loop needs cancellation and resource limits rather than assuming polite traffic.

Authenticate the handshake

Browsers cannot attach every custom header pattern used by ordinary API clients. Common options include a secure cookie, a short-lived query token, or a subprotocol negotiation. Validate origin where relevant and never place a long-lived credential in a URL that may enter logs and history.

Authorize the specific channel or resource after identity is known. A user permitted to connect is not automatically permitted to subscribe to every project. Close with a deliberate policy code when authentication fails instead of accepting and silently ignoring messages.

Handle backpressure and slow clients

One slow connection must not block an event producer or grow an unbounded list. Give each connection a bounded queue, define whether old messages are dropped or the client is disconnected, and record that decision in metrics. Limit message size and rate before expensive parsing.

Send operations can block when the network is congested. Use timeouts around application-level delivery and isolate broadcast work so one stalled browser does not delay every healthy client. Real-time does not mean infinite buffering; it means a clear latency and loss contract.

Survive proxies, restarts, and multiple workers

The reverse proxy must support HTTP upgrade, preserve required headers, and allow an idle timeout longer than the heartbeat interval. During deployment, clients will disconnect. Implement reconnect with exponential backoff and application-level resynchronization rather than promising an immortal socket.

An in-memory connection registry belongs to one process. With multiple workers or instances, distribute events through a broker or pub-sub service and keep presence semantics honest. Sticky sessions may reduce routing changes but do not replace shared event delivery.

Operate the connection lifecycle

  • Track active connections and handshake failures
  • Measure queue depth, send latency, drops, and disconnect codes
  • Use ping or application heartbeats below proxy idle limits
  • Cancel per-connection tasks on disconnect
  • Drain connections during deployment
  • Test reconnect and duplicate-event handling

WebSocket systems fail in the gaps between components. Load-test long-lived connections, simulate a worker restart, throttle a client, and verify that authorization is rechecked when credentials expire. A working chat demo is not yet an operating model.

Define the message protocol independently from the socket transport. Give every event a type, version, identifier, and documented payload, then decide how clients recover when they miss an event. A monotonically increasing cursor or last-known revision lets a reconnecting client request the gap through HTTP or a fresh snapshot. Without that recovery path, reconnect simply creates a live connection with stale state. Validate incoming JSON before it reaches business logic and reject unknown message types deliberately. Cap subscription counts per connection and connections per identity, and apply rate limits at both handshake and message level. During shutdown, stop accepting new sockets, notify connected clients when practical, allow a short drain window, then close remaining connections with a retryable code. Test the protocol with two workers and a broker, because a single-process test cannot reveal cross-instance ordering or fan-out failures. The socket carries events; the application still owns consistency.

How this fits the rest of the stack

Real-time connections need runtime, network capacity, event delivery, and observability. The RunxBuild hosting calculator helps model those resources, and the RunxBuild dashboard keeps the service route and logs visible.

Useful related references:

FAQ

Do I need to call websocket.accept?

Yes. Accept the handshake before normal application messages, unless you intentionally reject the connection.

How should a browser authenticate a WebSocket?

Use a secure cookie, short-lived query token, or subprotocol pattern, then authorize the requested resource.

What happens with multiple FastAPI workers?

Each has its own in-memory connections. Use shared pub-sub or a broker for cross-worker events.

How do I prevent slow clients from consuming memory?

Use bounded queues, send timeouts, message limits, and an explicit drop or disconnect policy.

Will WebSockets stay connected during deploys?

No guarantee. Clients must reconnect with backoff and resynchronize application state.

#FastAPI#WebSocket#Python#Real Time#ASGI