Streaming cheatsheet.

Server-Sent Events (SSE)

location /events {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Connection '';
    
    proxy_buffering off;
    proxy_cache off;
    
    proxy_read_timeout 24h;
    proxy_send_timeout 24h;
    
    chunked_transfer_encoding off;
    
    add_header X-Accel-Buffering no;
}

X-Accel-Buffering: no also tells nginx not to buffer (if set by backend).

WebSocket

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

location /ws {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    
    proxy_read_timeout 86400;
    proxy_send_timeout 86400;
    
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

Long polling

location /poll {
    proxy_pass http://app;
    proxy_buffering off;
    proxy_read_timeout 60s;
}

gRPC streaming

server {
    listen 50051 http2;
    
    location / {
        grpc_pass grpc://backend:50052;
        grpc_read_timeout 24h;
        grpc_send_timeout 24h;
    }
}

Streaming LLM responses

OpenAI-style:

location /v1/chat/completions {
    proxy_pass http://llm-backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_request_buffering off;
    
    proxy_http_version 1.1;
    proxy_set_header Connection '';
    
    proxy_read_timeout 300s;
}

proxy_request_buffering off important for upload streaming.

Disable response buffering globally

proxy_buffering off;

But typically you want buffering enabled for normal HTTP — disable per-location.

Time-out tuning for long connections

keepalive_timeout 86400;
client_body_timeout 86400;
proxy_read_timeout 86400;
proxy_send_timeout 86400;

Connections per IP for WebSocket

limit_conn_zone $binary_remote_addr zone=ws_conn:10m;
location /ws {
    limit_conn ws_conn 5;        # max 5 ws per IP
    ...
}

stream module (TCP/UDP)

For raw TCP (e.g., MQTT):

stream {
    upstream mqtt {
        server 10.0.0.1:1883;
    }
    
    server {
        listen 1883;
        proxy_pass mqtt;
        proxy_timeout 1h;
    }
    
    server {
        listen 8443 ssl;
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        proxy_pass mqtt;
    }
}

UDP

stream {
    server {
        listen 53 udp;
        proxy_pass dns_backend;
        proxy_responses 1;
        proxy_timeout 5s;
    }
}

Stream logging

stream {
    log_format basic '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time';
    access_log /var/log/nginx/stream.log basic;
}

Stream UPGRADE (mixed)

For TLS termination of WebSocket (alternative pattern with stream module + sni):

stream {
    map $ssl_preread_protocol $upstream {
        "TLSv1.2" tls_backend;
        "TLSv1.3" tls_backend;
        default   plain_backend;
    }
    
    server {
        listen 443;
        ssl_preread on;
        proxy_pass $upstream;
    }
}

Common mistakes

  • proxy_buffering on (default) for SSE → response delayed until full.
  • WebSocket without Upgrade/Connection headers.
  • Short proxy_read_timeout for long-lived connections.
  • proxy_pass mid-stream causing reconnect.
  • gzip on on SSE → broken (chunked sse with gzip flush issues).

Read this next

If you want my SSE/WS proxy templates, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .