字节笔记本
2026年7月19日
Configuring Nginx for Server-Sent Events: A Practical Guide
Server-Sent Events (SSE) is a straightforward way to push real-time updates from server to client over standard HTTP. Unlike WebSocket, it doesn't need a persistent bidirectional connection — just a long-lived HTTP response that the server can keep writing to. If your use case is server-to-client only (notifications, live feeds, progress updates), SSE is usually the simpler choice.
This post covers how to configure Nginx as a reverse proxy in front of an SSE backend, along with a client-side wrapper for handling reconnection.
Why Nginx Needs Special Config for SSE
By default, Nginx buffers responses from upstream servers. This breaks SSE because the client won't receive events until Nginx decides to flush its buffer. You need to explicitly disable buffering and adjust timeouts for the long-lived connections SSE requires.
Nginx Configuration
Global Settings
Start with basic worker and connection tuning:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}The epoll event driver is important on Linux for handling many concurrent connections efficiently — exactly what SSE needs.
HTTP Block
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript;
}Note that gzip compression should not be applied to SSE responses. We'll handle that per-location.
Upstream Definition
upstream backend_servers {
server 127.0.0.1:8080;
server 127.0.0.1:8081 backup;
keepalive 32;
}SSE Location Block
This is the critical part:
location /api/notifications/stream {
proxy_pass http://backend_servers;
# Essential SSE settings
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
# Timeouts — SSE connections are long-lived
proxy_connect_timeout 60s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# Headers
add_header Content-Type text/event-stream;
add_header Cache-Control no-cache;
add_header X-Accel-Buffering no;
# CORS
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
}Key points:
proxy_buffering off— prevents Nginx from buffering the response body. Without this, events won't reach the client in real time.X-Accel-Buffering: no— tells Nginx's own buffering layer to stay off. Some setups require this in addition toproxy_buffering off.proxy_set_header Connection ''— clears the Connection header so keepalive works correctly with HTTP/1.1.- Long read/send timeouts — SSE connections can stay open for minutes or hours. The default 60s timeout will kill them.
Client-Side Reconnection Wrapper
Browsers' native EventSource API handles basic reconnection, but you get more control with a wrapper:
class EventSourceWrapper {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.eventSource = null;
this.retryCount = 0;
this.maxRetries = options.maxRetries || 3;
this.retryDelay = options.retryDelay || 3000;
this.connect();
}
connect() {
this.eventSource = new EventSource(this.url);
this.eventSource.onmessage = (event) => {
this.retryCount = 0;
if (this.options.onMessage) {
this.options.onMessage(event);
}
};
this.eventSource.onerror = (error) => {
this.handleError(error);
};
}
handleError(error) {
if (this.retryCount < this.maxRetries) {
setTimeout(() => {
this.retryCount++;
this.connect();
}, this.retryDelay);
} else if (this.options.onMaxRetriesReached) {
this.options.onMaxRetriesReached(error);
}
}
close() {
if (this.eventSource) {
this.eventSource.close();
}
}
}The retry counter resets on successful message receipt, so transient glitches don't eat into your retry budget.
Monitoring
Add a health check endpoint and dedicated SSE logging:
location /health {
access_log off;
return 200 'OK';
}
log_format sse '$time_local $request_uri $status $request_time';
access_log /var/log/nginx/sse_access.log sse;The $request_time field is useful for spotting connections that are timing out or hanging unexpectedly.
Quick Checklist
proxy_buffering offis set on the SSE locationX-Accel-Buffering: nois added as a response header- Read and send timeouts are long enough for your use case
- Gzip is not applied to the SSE content type
- CORS headers are present if the frontend is on a different origin
- Health check endpoint exists for load balancer probing