Server-Sent Events vs WebSockets in 2026: when each actually wins
HTTP/2 removed the main argument against SSE years ago. Most teams have not updated their defaults.
The WebSocket default and where it goes wrong
"Real-time" in a feature spec triggers an immediate response. The engineer opens their WebSocket library, the backend team adds a ws:// endpoint, and within a few days there is a full-duplex channel between client and server. The reflex makes sense: WebSockets work, they are well-supported, and most teams have used them before.
But Server-Sent Events (SSE) also work, are equally well-supported, and for most use cases are the better fit. The gap between "WebSockets exist" and "WebSockets are the right choice" is wider than the reflex suggests.
The pattern driving most real-time product requirements is this: the server has new information, it needs to push it to the client, and the client listens. Job completion. Notification delivery. Payment status. Document parsing progress. Live metrics. In every one of these cases, communication is one-directional: server to client. WebSockets are a full-duplex protocol designed for the case where the client also sends high-frequency, low-latency data back. If you only need a one-way channel, building the two-way highway carries complexity you will pay for without using it.
What changed with HTTP/2 for Server-Sent Events
The canonical argument against SSE, the one in every comparison article from 2015 to 2019, was the connection limit under HTTP/1.1. Browsers capped connections to the same origin at 6. Each SSE stream held one of those slots permanently. A page with three streams consumed half the connection budget before the user did anything. That was a real constraint.
HTTP/2 multiplexes all requests to the same origin over a single underlying TCP connection. There is no per-origin connection limit to exhaust. Three SSE streams, ten SSE streams: they share one connection. The constraint that shaped a generation of architectural recommendations is gone.
As of 2026, HTTP/2 accounts for over 90% of web traffic on current CDN infrastructure. Cloudflare, Fastly, and CloudFront all serve HTTP/2 by default. The SSE limitation that most engineers cite from memory pre-dates the infrastructure reality by several years. The tribal knowledge about SSE is structured around a constraint that mostly no longer applies.
The four scenarios where Server-Sent Events wins
1. Server-to-client notifications and status updates
Activity feeds, job-progress bars, email delivery status, payment processing state, document analysis progress, live log tailing: all unidirectional. The server has something new; it pushes it to the client. The client does not send data back through the same channel (a separate HTTP call handles any user action). This describes the majority of features labelled "real-time" in product requirements, and SSE fits this shape precisely.
2. Enterprise networks and proxy compatibility
Corporate firewalls and proxy configurations are the part of this comparison that rarely appears in the tables. Many enterprise network stacks, especially those running deep packet inspection, handle WebSockets poorly. WebSocket connections use the Upgrade: websocket HTTP header to establish themselves, which does not fit the standard HTTP request-response model. Several widely deployed proxy appliances (Palo Alto Networks, Blue Coat, Cisco WSA) have documented behaviours that cause WebSocket connections to time out after 30 seconds, silently drop the upgrade handshake, or leave clients in a failed state that looks successful from the client perspective.
SSE is plain HTTP from end to end. It uses a standard streaming response with Content-Type: text/event-stream. A corporate proxy treats it identically to a normal HTTP response that happens to take a long time. There is no upgrade handshake to intercept or mishandle. If your product sells to financial services, healthcare, government, or large enterprises with managed network infrastructure, this is often the decisive factor, and it surfaces after deployment, when enterprise customers start calling support.
3. Auto-reconnect without writing reconnection logic
WebSockets require you to implement reconnection logic yourself. When the connection drops, whether from a network hiccup, a load-balancer timeout, or a server restart, your client code needs to detect the disconnection, apply an exponential backoff, and re-establish the session. This is not complex to write, but it is state to maintain, edge cases to handle, and tests to write. SSE handles reconnection natively in the browser EventSource API. When the connection drops, the browser reconnects automatically. The server can set the retry interval using the retry: field in the stream. The last seen event ID is sent on reconnect via the Last-Event-ID header, letting the server resume from where it left off. For anything running on mobile or flaky connections, this matters.
4. Horizontal scaling without a shared connection registry
WebSocket connections are stateful: a client is pinned to the specific server process that holds its connection for the duration of the session. Scaling to multiple nodes requires a shared pub/sub layer (Redis, a message broker, or a custom connection registry) to route messages from any publisher to any subscriber. SSE connections are still stateful at the TCP layer, but the coordination surface is smaller. Any server can push to any client via a Redis pub/sub channel, and you do not need to track which process holds which connection. For small-to-medium deployments, SSE plus a Redis channel is often substantially simpler to operate than a WebSocket connection registry.
When WebSockets earn their complexity
None of this means WebSockets are the wrong choice for every problem. There are cases where bidirectional, low-latency communication is genuinely necessary and the complexity is worth carrying.
- Collaborative editing (Google Docs, Figma, multiplayer whiteboards) requires extremely low-latency bidirectional message passing. The client is both sender and receiver in rapid succession. SSE overhead for the client-to-server direction, which would require a new HTTP request per user action, introduces unacceptable latency for cursor-position updates.
- Interactive browser games and real-time simulations that require sub-100ms RTT for player inputs. The persistent WebSocket channel is doing work here that a new HTTP request per action cannot replicate.
- Financial trading interfaces with order entry. The server pushes quotes; the client sends orders through the same low-latency channel. Milliseconds matter.
- Chat applications with typing indicators, read receipts, and presence: bidirectional, medium-frequency, latency-sensitive enough that HTTP overhead per message is felt.
The pattern across these cases: the client sends data through the same channel at a frequency or latency requirement that makes a new HTTP request per message impractical. If your client is mostly listening and sends the occasional action via a normal HTTP call, WebSockets are overhead you are not using.
| Protocol | Direction | HTTP/2 native | Firewall-friendly | Auto-reconnect | Best fit |
|---|---|---|---|---|---|
| Server-Sent Events | Server to client | Yes (multiplexed) | Yes (plain HTTP) | Built-in | Notifications, feeds, progress, live logs |
| WebSockets | Bidirectional | No (separate TCP conn) | Partial (DPI issues) | Manual | Collaborative editing, games, trading UIs, live chat |
| Long polling | Server to client | Yes | Yes | Manual (re-request) | Legacy environments, constrained HTTP clients |
Long polling in 2026: not dead, just niche
Long polling is sometimes described as the ancestor that WebSockets and SSE replaced. That framing is slightly off. In the long-polling model, the client sends a request, the server holds it open until there is something to send, then responds and the client immediately re-requests. The model is simple and it works.
Long polling has two properties that occasionally make it the right answer. First, it works with any HTTP infrastructure: no WebSocket library, no EventSource API, just standard request-response that any HTTP client, including very old firmware, constrained IoT devices, and legacy enterprise integrations, can execute. Second, it degrades cleanly. A timed-out request gets re-issued. No dropped connection state to recover. The simplicity is the point.
The downside is noise. At scale, each "nothing new yet" cycle produces a real HTTP request-response pair with headers, TLS overhead, and load-balancer routing. For small-scale internal tooling or low-frequency event delivery, this cost does not matter. For anything above modest traffic, it does. Long polling is a niche protocol in 2026, not an obsolete one.
The enterprise firewall problem in detail
The firewall compatibility issue deserves specifics, because the failure mode is subtle enough that many teams encounter it only in production, after enterprise customers begin calling support.
Several common proxy appliances break WebSocket connections silently. The typical failure pattern is: the client establishes a WebSocket connection without errors in development (direct connection, consumer ISP, no proxy). In production, users behind corporate proxy infrastructure report intermittent connection failures, or a feature that appears to be connected but never receives updates. The connection appears to succeed from the client perspective: the 101 Switching Protocols response was received. The proxy intercepted and terminated the connection shortly after, leaving a zombie socket.
The standard workaround is tunnelling WebSocket traffic over HTTPS on port 443, relying on TLS to make the proxy treat the connection as opaque. This works for most proxy configurations but not all, requires correct TLS termination at the WebSocket server, and adds operational surface area. SSE avoids this class of problem entirely because it is ordinary HTTP, and corporate proxies handle it the same way they handle any streaming download.
Making the call
Three questions determine which protocol you need. Work through them in order.
Does the client send high-frequency or latency-sensitive data through the same channel?
If the client is sending cursor positions, keystrokes, game inputs, or order submissions back through the real-time channel at a rate where a new HTTP request per action would add unacceptable latency: use WebSockets.
If the client mostly listens and sends the occasional action via a normal HTTP call: continue to the next question.
Will users be behind enterprise or DPI proxy infrastructure?
If your product targets enterprises, finance, healthcare, government, or large companies with managed network stacks: use SSE as the default. Scope any collaborative or bidirectional features explicitly, and use WebSockets only for those.
If users are on direct connections, consumer networks, or standard cloud environments: either protocol works. SSE is still the simpler choice for unidirectional push.
Does the HTTP client natively support SSE?
Browser: yes. Node.js via the EventSource API or fetch with streaming: yes. Most mobile frameworks: yes. Constrained legacy clients or IoT firmware: sometimes not. If SSE is not available in the target environment, use long polling.
Collaborative and interactive features (multiplayer, live editing, financial order entry) justify WebSockets regardless of how these questions answer. For everything else, the default in 2026 should be SSE. The widespread adoption of HTTP/2 removed the main technical objection years ago, and the operational benefits are real.
Frequently asked questions
Related reading
Every Postgres isolation level, and the specific bug it lets through
Three isolation levels, three distinct failure modes. Most Postgres deployments run at Read Committed without knowing it. Here is what each level permits and what upgrading actually costs.
Rate limiting in production: why the algorithm you chose is probably wrong for your workload
Most rate limiting failures aren't implementation errors. They come from picking an algorithm whose properties don't match the actual traffic shape. Here's a workload-first framework for making the right choice.
Idempotency keys: the layer you're protecting isn't the one that bites you
An Idempotency-Key header handles one of five layers where duplicates cause harm. Database writes, queue consumers, external API calls, and saga compensation each have failure modes the HTTP key doesn't cover.