Insights

Server-Sent Events vs WebSockets: which one does your app actually need?

Most projects that reach for WebSockets do not need them. Here is how to tell which side you are on before you commit to the harder option.

The short answer

If data flows from the server to the browser and the browser only occasionally sends something back, use Server-Sent Events. If both sides need to send messages continuously and with low latency, use WebSockets.

Live dashboards, notification feeds, activity streams, progress bars on long jobs, streaming AI responses — all of these are one-directional. SSE handles them with less code and far less operational weight. Chat, collaborative editing, multiplayer state and anything where the client is a first-class sender need a real socket.

What SSE gives you for free

SSE runs over ordinary HTTP. That sounds like a small detail and it is actually the whole argument. Your existing authentication works. Your reverse proxy works. Compression, logging, rate limiting and TLS termination all work exactly as they already do.

The browser also reconnects on its own. EventSource retries automatically when a connection drops, and if you send an id: field with each event the browser replays it back to you in a Last-Event-ID header, so you can resume from where the client fell off. With WebSockets you write that reconnection and replay logic yourself, and it is the part people get wrong.

Where SSE will bite you

Two real constraints. First, browsers cap connections per domain over HTTP/1.1 — roughly six. Open an SSE stream in several tabs and the seventh request to your domain hangs. Over HTTP/2 this mostly disappears because streams are multiplexed over one connection, so serve SSE over HTTP/2 and the limit stops mattering in practice.

Second, buffering. Any proxy between you and the browser that buffers responses will hold your events until the buffer fills, and your real-time feed arrives in lumps thirty seconds late. You need to disable buffering explicitly at each hop — X-Accel-Buffering: no for nginx, flush after every write in PHP, and no gzip on the stream. This is the single most common reason an SSE feed appears broken.

The cost nobody budgets for

Both approaches hold a connection open per client, and that is the number that governs your hosting bill. A thousand concurrent viewers is a thousand open connections regardless of protocol.

With PHP behind Apache using the prefork or worker model, each open stream occupies a process or thread for its entire life. Two hundred concurrent SSE clients can exhaust a default MaxRequestWorkers and the site stops responding to ordinary page requests — the symptom looks like the whole server is down, when in fact it is saturated with idle streams. Either raise the worker ceiling deliberately, move the stream endpoint to an event-driven runtime, or cap stream lifetime and let the client reconnect.

Timeouts you will hit in production

Anything sitting in front of your origin has an idle timeout. Cloudflare's free tier cuts a connection after roughly 100 seconds without bytes. Load balancers commonly default to 60. A stream that is genuinely quiet for two minutes will be killed and the user sees it stop.

The fix is a keepalive: send a comment line — a bare : followed by a newline — every 15 to 30 seconds. It costs almost nothing, is ignored by EventSource, and keeps every intermediary convinced the connection is alive. If you take one operational detail from this article, take that one.

How to decide in one minute

  • Does the client need to send messages continuously? → WebSockets.
  • Is it server → browser only? → SSE.
  • Do you need to support very old browsers with no polyfill? → long polling.
  • Is the payload binary? → WebSockets.
  • Do you want your existing auth, proxy and logging to just work? → SSE.

Choosing SSE where it fits removes an entire class of operational problems. Choosing WebSockets where SSE would do means writing and maintaining reconnection logic that the browser already implements for you.

Common questions

Is SSE slower than WebSockets?

Not meaningfully for server-to-client updates. Both push over an already-open connection. WebSockets have slightly lower per-message overhead, which matters at very high message rates, not at the rates most dashboards and feeds run at.

Can I use SSE with PHP?

Yes. Set the content type to text/event-stream, disable output buffering and compression, and flush after each event. The main caution is worker exhaustion — each open stream ties up a process for its lifetime, so plan capacity for concurrent streams, not requests.

Does SSE work behind Cloudflare?

Yes, but send a keepalive comment every 15 to 30 seconds. Free-tier Cloudflare drops connections that go quiet for around 100 seconds, and a stalled stream is indistinguishable from a broken feature to the person looking at it.

Next step

Need this done properly?

Describe the problem in plain language and I’ll tell you what it actually needs — including if that is less work than you expected.

WhatsApp