Most apps do best with one WebSocket per client, plus a second only when you truly need separation for reliability or policy.
You can open a WebSocket from a browser tab, a mobile app, or a backend service and keep it alive for hours. It feels cheap: no repeated handshakes, no polling, messages flow both ways. Then you scale past a few hundred users and a simple question starts to bite: how many WebSocket connections should each client hold?
There isn’t one universal limit. Browsers, OS sockets, proxies, load balancers, and your own server all place real caps. The goal is not “as many as possible.” The goal is the smallest connection count that still gives you clean routing, isolation for failure, and acceptable latency.
What A “Connection Per Client” Really Means
When people say “per client,” they may mean one browser tab, one logged-in user, one device, or one app process. Those are different.
- Browser tab: each tab can open its own sockets. Five open tabs can multiply your totals fast.
- Browser profile: limits often apply across a profile, not just a single tab, so “per tab” assumptions can mislead.
- Mobile app: one process may keep a socket alive in the foreground, then reconnect as the app sleeps and wakes.
- Backend worker: a single service may represent many end users over one upstream WebSocket, or it may open one per user.
In this article, “client” means one running app instance that owns the socket(s): a tab, a device app, or a process.
Default Rule: Start With One WebSocket
For most products, one WebSocket per client is the sweet spot. You get a single heartbeat, a single reconnect policy, a single backpressure story, and one place to attach auth. It also keeps your infra math sane: connections scale with active clients, not with every feature.
With one socket, you still can separate traffic. Use message types, channels, topics, or subprotocols. Keep payloads small and predictable. Push routing decisions to your app layer, not to “more sockets.”
Why One Socket Usually Wins
- Lower overhead: each connection consumes memory, file descriptors, and CPU for TLS and keep-alives.
- Cleaner failure handling: one reconnect brings the whole client back to a known state.
- Fewer proxy surprises: intermediaries often treat many long-lived connections as abuse.
- Easier rate control: you can apply per-client message budgets on one stream.
When Two Or More Sockets Make Sense
Multiple connections can be the right call when you can’t get the same behavior with message-level separation.
Split By Trust Or Credentials
If you must attach different auth tokens, scopes, or tenancy rules, separate sockets can reduce risk. This is common when a client joins a public stream (news, presence, public market data) and also uses a private stream (account events, direct messages).
Split By Reliability Needs
Some traffic can tolerate drops; other traffic can’t. Say a trading screen shows live prices plus order acknowledgements. If the “price firehose” floods the connection, it can delay or crowd out acknowledgements. A second socket can isolate that risk.
Split By Network Path Or Policy
A client on a locked-down network may reach one domain but not another. You may also need a separate endpoint for regional routing or for a dedicated gateway with different timeouts.
Split For Heavy Binary Streams
Audio, video, or large binary chunks can create bursty writes that interfere with small control messages. A second connection can keep control traffic snappy.
Even in these cases, treat “two” as a deliberate design choice. Past two, costs rise fast and debugging gets messy.
WebSocket Connections Per Client In Real Systems
Here’s a practical way to pick a number: start with one, add a second only when a clear boundary exists, and document the boundary. “Clear boundary” means you can explain, in one sentence, why mixing the streams harms correctness, latency, or access control.
If your reason is “it felt cleaner,” keep one socket and use message routing. Your future self will thank you.
Hidden Limits That Decide The Real Answer
Connection count is rarely limited by your WebSocket library. It’s limited by everything around it: the browser, the OS, and the gateway in front of your app.
Browser Caps And Resource Guards
Browsers guard against runaway pages. Limits vary by vendor and version. Some caps are per origin, some are global across the browser, and many are undocumented. Treat client-side socket count as a scarce resource.
A good product design also avoids one user opening dozens of tabs and turning into a mini load test. If your app expects multi-tab usage, add a tab coordinator so only one tab holds the socket and the others talk to it through BroadcastChannel, SharedWorker, or a service worker message bus.
OS File Descriptors And Ephemeral Ports
Each connection is a TCP socket. On servers, sockets consume file descriptors. On clients, many sockets can chew through ephemeral ports during reconnect storms. Reconnect jitter helps, but the best fix is fewer sockets per client.
Proxies, Load Balancers, And Idle Timeouts
Long-lived connections run into idle timeouts and buffering rules. Your gateway may close idle sockets after a few minutes, or it may cap concurrent connections per IP. Some CDNs cap WebSocket concurrency, too. A design that needs ten sockets per client will fail in more places than a design that needs one.
For protocol basics and the browser origin security model, the WebSocket spec is still the reference: RFC 6455, The WebSocket Protocol.
Table: Choosing The Right Connection Count
Use this as a decision checklist. It’s not a scoring model; it’s a fast way to spot when extra sockets buy real separation.
| Scenario | Connection Count Per Client | Why This Works |
|---|---|---|
| General app updates, chat, notifications | 1 | Message types keep streams separate without extra sockets. |
| Public feed plus private account events | 1–2 | Two sockets can split credentials and reduce cross-stream risk. |
| High-rate telemetry plus low-rate control | 1–2 | Isolation prevents bulk data from delaying control messages. |
| Multi-tab web app with many open tabs | 1 per profile | A coordinator avoids multiplying connections across tabs. |
| Mobile app with background restrictions | 1 | One reconnect policy handles sleep/wake cycles cleanly. |
| Binary streaming plus app commands | 2 | Separate sockets keep command latency stable during bursts. |
| Tenant-isolated admin plane plus user plane | 2 | Separation reduces blast radius if one plane is blocked. |
| Micro-frontend page with many widgets | 1 shared | Share a single socket via a page-level client to avoid fan-out. |
How To Design One Socket So It Scales
If you want one WebSocket per client to carry multiple features, you need a small set of rules that keep traffic predictable.
Use Clear Message Envelopes
Give every message a type and a short routing header. A simple envelope can include: type, channel, id, and payload. Keep it stable so older clients still work after releases.
Separate “Live” Streams From “Request/Reply”
Mixing pushes and RPC-style calls is fine if you tag messages and cap in-flight calls. If a client can fire 200 requests in a burst, it can also stall itself. Put a hard ceiling on concurrent calls and fail fast with a clear error message.
Budget Messages Per Client
Put a per-client send budget on the server. If a client subscribes to too many topics, degrade that client first, not the whole cluster. You can drop low-priority topics, slow them down, or switch them to snapshots.
Keep Heartbeats Boring
Heartbeats are there to keep idle timeouts away and to detect dead peers. Keep them small and regular. If you already send frequent data, you may not need extra pings.
Table: Where Connection Limits Come From
When you hit a ceiling, it usually comes from one of these layers. This table helps you debug without guessing.
| Layer | What Runs Out | What You See |
|---|---|---|
| Browser | Per-origin or global socket caps | New sockets fail or stay stuck in “connecting” state. |
| Client OS | Ephemeral ports during reconnect storms | Rapid connect/disconnect, errors under load, slow recovery. |
| NAT / Corporate proxy | Idle timeout or per-IP concurrency caps | Sockets drop after a fixed idle window. |
| Load balancer | Connection tracking table, idle timeout | Disconnects that cluster around the same time mark. |
| App server | File descriptors, memory per connection | Accept failures, “too many open files,” rising latency. |
| CPU | TLS, message parsing, fan-out work | Connections stay up but messages lag. |
| Downstream services | Subscription fan-out, DB pressure | Server is up, but topic updates slow down. |
Server Math That Keeps You Out Of Trouble
Picking “one socket” is only step one. You also need to know what one socket costs on your server.
Estimate Memory Per Connection
Every connection carries buffers, TLS state, and per-client subscription data. A plain WebSocket with small buffers might cost tens of kilobytes; a client with large send queues can cost far more. Track it in metrics: bytes queued per client, plus average subscription count.
Watch File Descriptor Headroom
On Linux, each socket consumes a file descriptor. Reverse proxies can consume more than one descriptor per client when they proxy to an upstream. If you use NGINX, its own guidance ties connection totals to open-file limits and worker settings: Avoiding the Top 10 NGINX Configuration Mistakes.
Plan For Reconnect Storms
The nastiest load spike is not “normal traffic.” It’s 50,000 clients reconnecting after a deploy, a gateway restart, or a network flap. Client-side jitter and exponential backoff are your first shield. Server-side rate limits and staged accept queues are your second shield. If you design for one socket per client, storms are still hard, but they’re not multiplied by three.
Patterns That Reduce Connections Without Losing Features
If you’ve reached for multiple sockets to keep your code tidy, try these patterns first.
Multiplex Channels On One Connection
Send a subscribe message that names a channel, then tag outgoing messages with that channel id. Keep a server-side map from channel id to topics. This gives you clean boundaries without extra TCP state.
Use A Shared Connection In The Browser
In a multi-tab setup, one tab can own the socket and the others can relay messages through browser primitives. You’ll cut connections, reduce battery drain, and avoid weird split-brain states where two tabs both think they’re “primary.”
Move Bulk Updates To Snapshots
If a topic can send 200 updates per second, ask if the UI really needs each one. Many dashboards do fine with a snapshot every 250–500 ms. That one shift can drop CPU and bandwidth without changing the product feel.
So, How Many Should You Allow?
For a browser client, design for one connection and treat two as the practical ceiling. For mobile, aim for one. For backend services, you can open more, but you still should justify each connection since it adds failure modes and state.
If you need more than two sockets per client, pause and re-check the root reason. Most teams end up wanting message-level separation, not more TCP connections.
References & Sources
- IETF.“RFC 6455: The WebSocket Protocol.”Defines the WebSocket handshake, framing, and origin-based browser security model.
- F5 NGINX Blog.“Avoiding the Top 10 NGINX Configuration Mistakes.”Explains how open-file limits and worker settings bound concurrent connections in NGINX deployments.
