WebSockets, gRPC and streaming responses
Upgrade headers for long-lived sockets, timeouts and buffering for streaming, gRPC proxying with HTTP/2, and disabling buffering for server-sent events.
WebSocket proxying
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream ws_backend {
server 10.0.0.21:9000;
keepalive 16;
}
server {
listen 443 ssl;
http2 on;
server_name chat.example.com;
location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1; # required for Upgrade
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s; # an idle socket must not be cut
proxy_send_timeout 3600s;
proxy_buffering off;
}
}- The
mapkeeps a normal HTTP request sendingConnection: closewhile an upgrade sendsConnection: upgrade. - Without
proxy_http_version 1.1the Upgrade header is dropped and the handshake fails with a 400 or a hung connection. - The default
proxy_read_timeoutis 60 seconds; a quiet websocket is closed after that unless you raise it. - Send application-level pings so middleboxes do not expire the idle connection independently of nginx.
gRPC proxying
server {
listen 443 ssl;
http2 on; # gRPC requires HTTP/2 end to end
server_name grpc.example.com;
ssl_certificate /etc/letsencrypt/live/grpc.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grpc.example.com/privkey.pem;
location /helloworld.Greeter/ {
grpc_pass grpc://grpc_backend;
grpc_set_header X-Real-IP $remote_addr;
grpc_read_timeout 300s;
grpc_send_timeout 300s;
grpc_socket_keepalive on;
}
# fall back to plain HTTP/1.1 for a non-gRPC health endpoint
location = /health {
grpc_pass grpc://grpc_backend;
error_page 502 = /health_fallback;
}
}
upstream grpc_backend {
server 10.0.0.31:50051;
}| Protocol | Directive | Notes |
|---|---|---|
| gRPC over TLS | grpc_pass grpcs:// | Encrypted to the backend |
| gRPC plaintext | grpc_pass grpc:// | Only on a trusted network |
| HTTP/2 to client | listen 443 ssl; http2 on; | Required for gRPC |
| Server-sent events | proxy_buffering off; | Prevents message batching |
Server-sent events and streaming responses
location /events/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # deliver each chunk as it is written
proxy_cache off;
proxy_read_timeout 24h;
chunked_transfer_encoding on;
add_header X-Accel-Buffering no; # tell the app not to buffer either
add_header Cache-Control "no-cache";
}⚠️
Compression breaks streaming. gzip buffers output in blocks, so a compressed event stream arrives in bursts or not at all. Exclude streaming paths from
gzip, and disable buffering in the application as well as in nginx.FAQ
Why does my websocket disconnect after exactly a minute?
The default
proxy_read_timeout is 60 seconds. Raise it for the socket location and add application-level heartbeats so the connection also survives an idle NAT.Can I serve gRPC and REST on the same port?
Yes. Route by path prefix — a gRPC service path such as
/helloworld.Greeter/ is distinctive — and use grpc_pass for those locations with proxy_pass for the rest.Related
Rewrites, redirects and try_files nginx in containers and as a Kubernetes ingress
Last refreshed 2026-09-18.