WebSockets and Streaming Through Reverse Proxies
Upgrade WebSockets and server-sent events cleanly through Nginx and HAProxy, and size buffers so long-lived streaming connections stay stable.
Netbay Cloud Team
Netbay Engineering
On this page
Most HTTP requests live for a few hundred milliseconds. WebSockets and server-sent events are different animals: they open a connection and expect it to stay open for minutes or hours, streaming data in both directions. Proxies built and tuned for quick request-response symmetry need explicit configuration to pass these through, and the good news is the settings are small and stable across Nginx and HAProxy.
How the Upgrade handshake works
A WebSocket connection begins as an ordinary HTTP request that asks, via the Upgrade and Connection headers, to switch protocols on the same socket.
GET /ws HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: UpgradeIf the server agrees, both sides are committed to a long-lived, bidirectional socket. A proxy has to forward those two headers verbatim and, after the handshake, stop interpreting the payload as HTTP. Block WebSockets without intending to, and the connection dies in the handshake; keep the headers and most proxies pass the socket through transparently.
WebSockets through Nginx
Nginx needs the upgrade headers set explicitly, since it does not forward them by default.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name chat.example.com;
location /ws {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
}The map block copies the client's Upgrade header into a variable; a normal request gets Connection close, a WebSocket upgrade gets Connection upgrade. The long proxy_read_timeout is load-bearing here — a WebSocket that streams once a minute would otherwise trip the default thirty-second timeout and close the socket mid-conversation.
WebSockets through HAProxy
HAProxy detects the upgrade itself in mode http, but you must raise the tunnel timeout or the same idle-timeout logic will kill the socket.
frontend ft_chat
bind :443 ssl crt /etc/haproxy/certs.pem
default_backend bk_chat
backend bk_chat
mode http
timeout connect 5s
timeout client 3600s
timeout server 3600s
option http-server-close
server ws1 127.0.0.1:9000 check inter 2sAfter the handshake, HAProxy recognizes the WebSocket tunnel and stops parsing frames as HTTP. The client and server timeouts define how long an idle tunnel may live; tooling like a chat app that only sends a heartbeat every few minutes needs those set in hours. option http-server-close keeps ordinary connections tidy while tunnels stay open.
Server-sent events through either
SSE is just HTTP with an open response, so it needs fewer tricks: the proxy must not buffer the response into discrete chunks, and the idling read timer must tolerate long gaps between events.
location /events {
proxy_pass http://127.0.0.1:9000;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_set_header Connection "";
proxy_http_version 1.1;
}proxy_buffering off hands each event to the client as soon as the upstream writes it, which is the difference between a real-time feed and a feed that arrives every few seconds in bursts. With both WebSockets and SSE, keep timeouts generous, disable buffering where it does not belong, and never apply a short idle cut-off to a stream you intend to keep alive.
Takeaway
WebSockets and SSE flow through a proxy once you handle the handshake headers and give the tunnel a timeout measured in hours, not seconds. Disable buffering for events, keep idle windows generous, and the proxy becomes invisible. When you grow past one chat server, HAProxy in front of a small WebSocket pool on a Netbay VPS keeps the pattern scaling — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance