Sign in
TodayMapLearnPracticeReview
Library
14 MINadvancedNetworking & CommunicationNot started

Server-Sent Events (SSE)

Server-Sent Events is a standard for one-way streaming from server to client over plain HTTP. The server holds the response open and writes `text/event-stream` chunks; the browser's `EventSource` API parses them and fires JavaScript events. It is dramatically simpler than WebSockets when the server only needs to push — no upgrade, no binary framing, automatic reconnection with replay built into the protocol.

Why this matters

Many real-time features (live notifications, dashboards, status feeds, AI token streaming) are inherently one-way: the server pushes, the client reads. Using WebSockets for these is over-engineering — you pay for a bidirectional channel you never use the upstream half of. SSE is the right tool, simpler to operate, and works through every proxy and CDN that supports HTTP. Its recent surge in popularity (every LLM token-streaming UI uses it) makes it a modern essential.

Prerequisites
  • HTTP — HyperText Transfer Protocol
Related
  • WebSockets — Full-Duplex Real-Time Over TCP
  • HTTP — HyperText Transfer Protocol
  • Real-Time Communication Overview
Used in

Foundational.

Lesson

How it works

SSE is a thin layer over HTTP. The client opens a normal GET request; the server responds with Content-Type: text/event-stream and then holds the connection open, writing chunks. Each chunk is a UTF-8 text message in a tiny format:

code
event: token
data: {"text":"Hello"}

id: 42
event: token
data: {"text":" world"}

The browser's EventSource JavaScript API parses this and fires onmessage or named-event callbacks. The server writes; the client reads. No upgrade, no binary framing, no handshake beyond the HTTP request itself.

Three features make SSE production-friendly:

  • Auto-reconnect: if the connection drops, the browser reconnects automatically, including the Last-Event-ID header so the server can replay missed events.
  • Named events: an event: field lets the server multiplex different event types over one stream (e.g., event: message, event: typing, event: presence).
  • HTTP-native: works through any proxy, CDN, or load balancer that speaks HTTP. SSE is just a long-lived HTTP response.

The trade-off is that SSE is strictly server-to-client. The client cannot send data back over the same connection — it uses separate HTTP requests for that (POST to send a chat message, GET SSE to receive updates). For many apps this is fine; for true bidirectional real-time, use WebSocket.

The SSE wire format is intentionally minimal. Each event is separated by a blank line; fields are field: value lines:

  • data: <text> — the payload. Multiple data: lines concatenate (with newlines) into one event.
  • event: <name> — fires a named event listener (eventSource.addEventListener('typing', ...)). Without this, onmessage fires.
  • id: <event-id> — sets the last event ID. On reconnect, the browser sends this as the Last-Event-ID request header so the server can replay.
  • retry: <ms> — the server can suggest a reconnection delay.
  • Lines starting with : are comments (used as keepalives).

The format is text, not binary. If you need to send binary data, base64-encode it (costs 33% overhead) or use WebSockets. For most server-to-client use cases — JSON payloads, status updates, token streams — text is fine.

Production details that bite:

  • Buffering: reverse proxies (nginx, default) may buffer responses, defeating SSE. Set proxy_buffering off and X-Accel-Buffering: no on responses.
  • Connection limits: browsers historically limited 6 SSE connections per domain over HTTP/1.1. HTTP/2 multiplexing lifts this to ~100 streams per connection — use HTTP/2 if you need many SSE streams per page.
  • Idle timeouts: same problem as WebSockets — NATs and proxies kill idle connections. Send a :keepalive comment every 15-30 seconds to keep the stream visibly active.
  • Load balancing: like WebSockets, SSE is stateful. The LB must not buffer (must support streaming responses), and the server must hold the connection open. Use sticky sessions or a pub/sub backbone to route events to the right server.

The recent killer use case for SSE is streaming LLM token output. When you ask ChatGPT or Claude a question, the response is generated token-by-token; users expect to see each token as soon as it is produced, not wait 10 seconds for the full answer. The transport for this is SSE.

The pattern: the client POSTs the prompt; the server responds with Content-Type: text/event-stream and emits one SSE event per generated token (or per chunk of tokens). The browser renders tokens as they arrive, giving the typing effect. When the model finishes, the server sends a final event: done event and closes the stream.

Why SSE and not WebSocket for this?

  • The data flow is one-way (server to client) once the prompt is submitted.
  • It runs over plain HTTPS — works through every corporate proxy and firewall.
  • Auto-reconnect lets a dropped connection resume without losing context (the server can replay missed tokens via Last-Event-ID).
  • It is just HTTP — existing auth, rate limiting, and observability all work without modification.

This pattern (POST a request, stream the response back as SSE) is now the standard for any AI tool, and it has driven a renaissance of SSE adoption — many developers' first encounter with SSE is wiring up an LLM streaming response.

Decision rule: SSE vs WebSocket

If the server pushes and the client only listens, choose SSE. If both sides push frequently (chat, gaming, collab editing), choose WebSocket. If you need binary efficiency at high frequency, choose WebSocket binary frames. If you only need the server to push occasional updates and bandwidth is not a concern, SSE is simpler, more robust, and more proxy-friendly.

Two operational concerns that decide whether SSE is viable in production: authentication and horizontal scaling.

Authentication is straightforward because SSE runs over plain HTTP. The browser's EventSource API accepts withCredentials for cookies; alternatively, pass a token in the URL query string (/stream?token=...) since EventSource cannot set custom headers. The query-string approach leaks tokens into server logs and proxy logs — prefer cookies for browser clients. For non-browser clients (server-to-server streaming), use fetch() with streaming readers and set any headers you like.

Token expiry during a long-lived SSE stream is a real issue. The stream is established with a token; if the token expires an hour later, the connection stays open but its auth is now stale. Solutions: use long-lived tokens for streams specifically; refresh the token before it expires and re-establish the stream (with Last-Event-ID for replay); or use a refresh-token mechanism that updates the active stream's auth.

Horizontal scaling mirrors the WebSocket problem: each SSE connection is stateful and pinned to one server. To scale to many clients, you need many SSE servers plus a routing layer. The standard pattern: a pub/sub backbone (Redis Pub/Sub, NATS, Kafka) connects all SSE servers. When the backend produces an event for user 42, it publishes to a topic; whichever SSE server holds user 42's connection consumes and writes the event to the stream. The connection registry (which user is on which server) is typically Redis.

A subtle production detail: SSE servers must support graceful shutdown. When a server is being drained (deploy, autoscaler scale-in), it should send a retry: 5000 and a CLOSE event so clients reconnect to a healthy server after a short delay — not silently kill the connection and force a thundering-herd reconnect. Like WebSockets, SSE fleets need staggered deploys and connection draining to be production-grade.

Check yourself
solid

You are building an LLM chat UI where the model's response streams token-by-token to the browser. Which transport is most appropriate, and why?

Pick one answer.

Check yourself
interview

Your SSE stream works in development but in production behind nginx, the client receives all events at once at the end of the stream, not as they are produced. What is the most likely cause and fix?

Pick one answer.

Check yourself

Which is a real limitation of SSE compared to WebSockets?

Pick one answer.

Engineering mental model

Mental model. Think of Server-Sent Events (SSE) as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Server-Sent Events (SSE) mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Server-Sent Events (SSE), name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Server-Sent Events (SSE).
Image unavailable. Original NO CAP systems visual for Server-Sent Events (SSE).
Server-Sent Events (SSE): a compact system-thinking visual.— Original NO CAP visual.
message_id = queue.publish({
    "type": "server-sent-events",
    "key": resource_id
})
# Consumer must be safe to retry.
A minimal engineering sketch for reasoning about Server-Sent Events (SSE).

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Server-Sent Events (SSE)

Change the variables below and predict what breaks first in Server-Sent Events (SSE). The production lab can later reuse these same inputs.

System pressure6%
Queue backlog growthstable
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Server-Sent Events (SSE), start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Server-Sent Events (SSE). What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Server-Sent Events (SSE)?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Server-Sent Events (SSE), traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Server-Sent Events (SSE), and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

For Server-Sent Events (SSE), separate producer speed from consumer speed. The key design question is what happens when production temporarily exceeds processing capacity: queue it, shed it, slow producers down, or degrade the feature.

Numerical sanity check

A simple queue sanity check: if producers create 8,000 messages/s and consumers process 6,000 messages/s, backlog grows at roughly 2,000 messages/s until the imbalance is corrected.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

A downstream service slows down while Server-Sent Events (SSE) keeps accepting traffic. Would you rather apply backpressure, queue more work, shed load, or degrade? Explain the trade-off.

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Runs over plain HTTP — works through every proxy, CDN, and firewall that speaks HTTP.
  • +Auto-reconnect with Last-Event-ID replay built into the protocol and browser.
  • +Named events and small text wire format — simple to debug (curl sees everything).
  • +Trivial browser API (EventSource) — no special library needed.
  • +Cheaper to operate than WebSocket for one-way streams — no upgrade, no custom framing.
Cons
  • −Strictly server-to-client — the client uses separate HTTP requests to send data back.
  • −Text format only — binary data needs base64 (33% overhead).
  • −HTTP/1.1 limits 6 connections per domain — use HTTP/2 if you need many concurrent streams.
  • −Reverse proxies often buffer by default — a frequent source of 'it works in dev' bugs.
  • −Like WebSocket, stateful — scaling requires pub/sub backbone and connection sharding.
Failure modes

How this breaks in production

  • Reverse proxy buffering the response — events arrive in a burst at the end instead of streaming.
  • Idle NAT/proxy timeouts killing the stream — mitigated by sending comment keepalives every 15-30s.
  • Browser connection limit (6 per domain on HTTP/1.1) — mitigate with HTTP/2 or domain sharding.
  • Server memory exhaustion from many concurrent SSE connections — same scaling problem as WebSocket.
  • LB not supporting streaming responses — falls back to buffering, breaking the stream.
  • Lost Last-Event-ID on reconnect — server replays events the client already saw.
Common mistakes

Don't fall into these traps

  • •Reaching for WebSocket when SSE would suffice — simpler, more proxy-friendly, auto-reconnect built in.
  • •Forgetting to disable proxy buffering in nginx/Cloudflare — 'works in dev, breaks in prod'.
  • •Not sending keepalives — stream silently dies on mobile after 30-60s of no events.
  • •Assuming the client can send data over the SSE stream — it cannot; use separate HTTP requests.
  • •Sending large JSON payloads when the wire format is text — consider binary via WebSocket if bandwidth matters.
  • •Not handling replay on reconnect — users see duplicate or missed events after a network blip.
Where you see it

Real systems using this

Every LLM streaming UI (OpenAI, Anthropic, Claude, ChatGPT) streams tokens over SSE.GitHub's real-time status page and live updates.Stock trading dashboards (Robinhood, E*TRADE) for live prices.Sports live scores and play-by-play updates.Notification systems — Slack, Linear, Vercel deploy logs.
Teardowns

How real systems implement this

  • OpenAI ChatGPT — Streams model responses token-by-token over SSE. The browser's EventSource (or fetch with a streaming reader) renders tokens as they arrive.
  • Vercel deploy logs — Live build and deploy logs stream over SSE to the dashboard, so users watch their build progress in real time without WebSocket overhead.
  • GitHub Actions live logs — Streams CI/CD log lines over an HTTP streaming response (SSE-style) so users see log output as it is produced.
Interview prompts

Practice saying it out loud

  • Q1SSE vs WebSockets vs long polling — when would you choose each? Give a real example of each.
  • Q2How would you implement LLM token streaming from a server to a browser? Walk through the protocol choice and the failure modes.
  • Q3Your SSE stream works in dev but arrives all at once in production behind nginx. What is wrong?
  • Q4How do you scale SSE horizontally across many servers?
  • Q5What does the Last-Event-ID header do, and how does the server use it on reconnect?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Networking & Communication reference
Reference
Networking & Communication reference
Reference
Networking & Communication reference
Reference
Cloudflare Learning Center
Cloudflare

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

WebSockets — Full-Duplex Real-Time Over TCP