WebSocket vs Long Polling vs SSE
Introduction¶
"Push data to the browser" has three common answers: long polling, Server-Sent Events (SSE), and WebSocket. They trade off simplicity, efficiency, direction, and how well they survive corporate proxies.
Long polling¶
The client makes an HTTP request; the server holds it open until it has data (or a timeout), responds, and the client immediately makes another request.
GET /events ----> (server holds... 25s... event arrives)
<---- 200 {"type":"message",...}
GET /events ----> (held again)
- Pros: plain HTTP, works through any proxy/firewall, trivial to load balance, degrades gracefully.
- Cons: a full request/response (headers, ~hundreds of bytes) per message; a brief window between responses where events must be queued server-side; higher latency under load.
Still the right choice when you must traverse unknown middleboxes or support ancient clients.
Server-Sent Events (SSE)¶
One long-lived HTTP response with Content-Type: text/event-stream; the server
writes events into it over time.
GET /stream
Accept: text/event-stream
---->
<---- 200 Content-Type: text/event-stream
data: {"price": 42}\n\n
data: {"price": 43}\n\n
...
- Pros: dead simple, native
EventSourceAPI with automatic reconnect andLast-Event-IDresume, works over plain HTTP/1.1 and HTTP/2, one TCP connection. - Cons: server → client only; the client still needs a normal POST for anything it sends. Text only (base64 your binary). Over HTTP/1.1, each SSE stream eats one of the browser's ~6 connections per origin — HTTP/2 fixes that with multiplexing.
Great fit for dashboards, notifications, live feeds, LLM token streaming.
WebSocket¶
Starts as an HTTP request with an Upgrade header; after a 101 the same TCP
connection becomes a bidirectional message pipe with its own tiny framing.
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
---->
<---- HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
<==== frames both directions, ~2–14 bytes overhead each ====>
- Pros: full duplex, minimal per-message overhead, binary or text, low latency.
- Cons: not HTTP after the upgrade — proxies, WAFs, and L7 load balancers must explicitly support it. No built-in reconnect/resume (you build it). Sticky sessions or a shared pub/sub backend (Redis) needed to scale horizontally.
The framing (WebSocket)¶
Each frame: a 2-byte header (FIN bit, opcode — text/binary/close/ping/pong — and payload length), an optional extended length, a 4-byte mask (client→server frames are XOR-masked to defeat proxy cache poisoning), then the payload. Ping/ pong frames are the keepalive mechanism — send them or an idle connection gets reaped.
Proxy and timeout gotchas¶
# Nginx must be told to pass the Upgrade headers through
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # map $http_upgrade -> "upgrade"/""
proxy_read_timeout 3600s; # default 60s kills idle WS/SSE connections
proxy_buffering off; # critical for SSE — otherwise events are buffered
}
- SSE events arrive in bursts, not live — a proxy is buffering the response.
proxy_buffering off(Nginx),X-Accel-Buffering: noheader, disable gzip on the stream. - WebSocket connects then drops after 60s —
proxy_read_timeout(or an ELB idle timeout, or Cloudflare's 100s). Raise it and send app-level pings. 101never happens, get a200or400— the proxy isn't forwardingUpgrade/Connection, or a WAF is stripping them.- Long polling hammered the server — no jitter/backoff on reconnect after an error; a thundering herd of clients all re-polling in lockstep.
Choosing¶
| Need | Use |
|---|---|
| Server → client updates, simple | SSE |
| Bidirectional, chat, games, collab editing | WebSocket |
| Must work through unknown/hostile proxies | Long polling |
| LLM token streaming | SSE (or WS if you're already on it) |
Verification and troubleshooting¶
# Test the WS handshake by hand
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" -H "Sec-WebSocket-Version: 13" \
http://example.com/ws # expect: HTTP/1.1 101
# Test SSE
curl -N -H "Accept: text/event-stream" http://example.com/stream # events should stream live
websocat ws://example.com/ws # interactive WS client
- Browser DevTools → Network → WS tab shows frames in/out for a WebSocket; the EventStream tab shows SSE events.
- Scaling WebSocket — a message published on instance A must reach a client connected to instance B. Put a Redis pub/sub (or NATS, or a managed service) behind all instances; don't rely on sticky sessions alone.
Related tools and reading¶
- On-site: HTTP Header Analyzer, Port Checker, SSL Certificate Checker.
- Related posts: HTTP/1.1 vs HTTP/2 vs HTTP/3, MQTT QoS levels explained.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Network Protocols
DHCP DORA: How a Host Gets Its Address
The four DHCP packets, the renewal timers, why relays exist, and the tcpdump filter to watch it happen.
October 24, 2026SMB Versions and Signing, Demystified
SMB1 is a security liability, SMB3 encrypts and signs. Here's how dialect negotiation works and how to tell which one a connection actually used.
October 24, 2026DNS over TLS and DNS over HTTPS
Plain DNS is unencrypted and unauthenticated. DoT and DoH fix the transport — here's how each works, and the trade-off between privacy and network visibility.
October 24, 2026