Proxies·6 min read·

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.

NB

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.

text
GET /ws HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade

If 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.

nginx
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.

haproxy
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 2s

After 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.

nginx
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.

WebSocket upgrade through a proxy Client GET /ws Proxy forwards upgrade stops parsing HTTP long idle timeout WS server 127.0.0.1:9000 handshake: Upgrade + Connection headers tunnel: bidirectional bytes, long timeouts 101 Switching Protocols

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