n4nAI

Debugging SSE connection drops in LLM streaming responses

Step-by-step debugging for SSE connection drop LLM streaming: fix client timeouts, proxy buffering, and provider degradation in production.

n4n Team3 min read729 words

Audio narration

Coming soon — every post will get a voice note here.

An sse connection drop llm streaming failure shows up as truncated JSON, a sudden socket close, or a client timeout mid-generation. These breaks are rarely the model itself; they’re usually layers between you and the provider mishandling long-lived HTTP responses.

Step 1: Reproduce with a minimal client

Before changing infrastructure, confirm the drop is real and not a bug in your app framework. Strip everything but a raw HTTP stream reader.

curl -N -X POST https://api.example.com/v1/chat/completions \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","stream":true,"messages":[{"role":"user","content":"Write 500 words about databases"}]}' \
  -D - | cat

The -N disables curl’s buffering. If the stream dies here, the problem is upstream of your code. If it survives here but fails in your app, the bug is in your client stack.

In Python, use httpx with explicit streaming:

import httpx

url = "https://api.example.com/v1/chat/completions"
payload = {"model": "gpt-4o-mini", "stream": true, "messages": [{"role": "user", "content": "long story"}]}
headers = {"Authorization": "Bearer " + token}

with httpx.Client(timeout=httpx.Timeout(None, connect=5)) as client:
    with client.stream("POST", url, json=payload, headers=headers) as r:
        for line in r.iter_lines():
            print(line)

Verify success: The process prints data: lines until a final data: [DONE] and exits cleanly with HTTP 200. No Connection reset or RemoteDisconnected exceptions.

Step 2: Kill proxy and gateway buffering

Most sse connection drop llm streaming incidents I’ve traced were nginx or ALB silently buffering or timing out. Nginx defaults to proxy_buffering on, which will hold chunks until the buffer fills or the response ends—defeating streaming.

location /v1/ {
    proxy_pass https://upstream;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 3600s;
    chunked_transfer_encoding on;
}

AWS ALB has a 60-second idle timeout by default. Streaming LLM responses can have long thinking gaps. Raise it to 1800 seconds or put a TCP-preserving proxy (like Envoy) in front.

If you run a Kubernetes ingress, check the annotation for timeout:

nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"

Verify success: Run the curl -N command from Step 1 through the proxy and watch with curl -v. You should see Transfer-Encoding: chunked and chunks arriving every few hundred milliseconds, not one blob at the end.

Step 3: Fix client-side read timeouts

The next common cause of sse connection drop llm streaming is a client read timeout. Many SDKs default to 30s total or per-read timeouts. A model that pauses for 31s between tokens will snap the connection.

In Node.js, fetch with an AbortSignal.timeout(30000) will kill a slow stream:

// BAD
const res = await fetch(url, { method: "POST", body, signal: AbortSignal.timeout(30000) });

// GOOD
const controller = new AbortController();
// only abort on explicit user cancel, not time
const res = await fetch(url, { method: "POST", body, signal: controller.signal });

In Python, requests with stream=True still respects read_timeout. Set it to None:

import requests

r = requests.post(url, json=payload, headers=headers, stream=True, timeout=(5, None))
for line in r.iter_lines():
    if line:
        print(line.decode("utf-8"))

Verify success: Run a prompt that forces a long pause (e.g., “think step by step” with a slow model). The client stays connected for >2 minutes without raising ReadTimeout.

Step 4: Parse partial frames defensively

A dropped connection often leaves a half-written SSE line in your buffer. Naive split("\n") will crash on the last fragment.

def parse_sse(raw_chunks):
    buf = ""
    for chunk in raw_chunks:
        buf += chunk.decode("utf-8")
        while "\n" in buf:
            line, buf = buf.split("\n", 1)
            if line.startswith("data:"):
                yield line[5:].strip()
    # buf now holds a partial line if connection dropped
    if buf.startswith("data:") and len(buf) > 5:
        yield buf[5:].strip()  # best-effort

Treat a missing data: [DONE] as an incomplete stream. Log the last token offset you successfully processed so you can resume.

Verify success: Artificially kill the server mid-stream (kill -9 on the proxy). Your parser should emit all complete events and log stream_incomplete=True instead of throwing JSONDecodeError.

Step 5: Use gateway fallback without trusting it blindly

An inference gateway like n4n.ai provides automatic fallback when a provider is degraded, but your client must still handle the transient sse connection drop llm streaming event. The fallback creates a new upstream connection; the old TCP socket to you is still closed. You’ll see a clean close, not an error page.

If your request included headers={"x-n4n-route": "openai->anthropic"}, the gateway may switch providers mid-flight only if it supports seamless handoff—most do not. Assume you must restart the request.

Design your prompt/state to be resumable:

def stream_with_resume(messages, last_tokens):
    if last_tokens:
        messages = messages + [{"role": "assistant", "content": last_tokens}]
    # send request, collect tokens

Verify success: Force a provider outage in staging. Your client logs a drop, retries with prior context, and produces a complete answer without duplicating the first paragraph.

Step 6: Implement reconnection with backoff

SSE has no standard resume protocol in LLM APIs, so reconnection means re-requesting. Use exponential backoff and a max retry count.

import time, random

def resilient_stream(url, payload, headers, max_retries=4):
    for attempt in range(max_retries):
        try:
            with httpx.Client(timeout=httpx.Timeout(None, connect=5)) as c:
                with c.stream("POST", url, json=payload, headers=headers) as r:
                    for line in r.iter_lines():
                        yield line
            return
        except (httpx.RemoteProtocolError, httpx.ReadError) as e:
            if attempt == max_retries - 1:
                raise
            sleep = (2 ** attempt) + random.uniform(0, 1)
            time.sleep(sleep)

For browsers, use EventSource only if the API speaks pure SSE on GET; LLM endpoints require POST, so you need a custom fetch loop with the same backoff.

Verify success: Unplug your network for 5 seconds during a stream. The client pauses, then resumes and finishes the generation after reconnecting.

Step 7: Instrument drop rate in production

You cannot fix what you don’t measure. Emit a metric every time a stream terminates without [DONE].

from prometheus_client import Counter

SSE_DROPS = Counter("sse_connection_drop_llm_streaming_total", "Count of dropped streams")

def observe_stream(stream_iter):
    completed = False
    for line in stream_iter:
        if line == "[DONE]":
            completed = True
        yield line
    if not completed:
        SSE_DROPS.inc()

Alert if drop rate exceeds 1% over a 10-minute window. Correlate with provider status pages and your proxy logs.

Verify success: A chaos test that drops 5% of connections shows the counter incrementing and the alert firing, while real traffic stays under threshold.

Production checklist

  • Raw curl -N reproduces or rules out client bugs.
  • Proxy has proxy_buffering off and idle timeout > 1h.
  • Client read timeout is None or explicitly long.
  • SSE parser tolerates partial final lines.
  • Reconnection backs off and resumes with prior tokens.
  • Drop rate is a tracked metric, not a mystery.

Following these steps turns a flaky sse connection drop llm streaming complaint into a measured, recoverable event.

Tagsssestreamingdebuggingconnections

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All debugging streaming responses posts →