100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
DevOps

Proxying WebSockets with Nginx

Configure nginx to correctly upgrade and proxy long-lived WebSocket connections, including timeouts and load-balancing considerations.

Reverse Proxy & Load BalancingAdvanced10 min readJul 10, 2026
Analogies

Why WebSockets Need Special Handling

A WebSocket connection begins life as a normal HTTP request that asks the server to switch protocols via an Upgrade: websocket header and a matching Connection: Upgrade header; once the server responds with a 101 Switching Protocols status, the same TCP connection is repurposed for a persistent, full-duplex binary/text message stream that stays open far longer than a typical HTTP request-response cycle. Because nginx strips hop-by-hop headers like Connection by default and doesn't forward Upgrade unless told to, a naive proxy_pass will cause the upgrade handshake to fail and the client to fall back to polling or simply disconnect.

🏏

Cricket analogy: It's like a match starting under normal rules but the umpires agreeing mid-innings to switch to a super-over format on the same ground — everything before the switch was standard, but afterward the rules and rhythm of play change completely.

Configuring the Upgrade Handshake

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

server {
    listen 80;
    server_name ws.example.com;

    location /socket/ {
        proxy_pass http://ws_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Making the upgrade work requires three things in the relevant location block: proxy_http_version 1.1 (WebSocket upgrades are undefined in HTTP/1.0), and explicit proxy_set_header lines for both Upgrade $http_upgrade and Connection "upgrade" so nginx forwards the client's handshake headers to the backend instead of silently dropping them. Because the Connection header must be "upgrade" only for actual WebSocket requests and "close" or empty otherwise, most real configurations use a map block keyed on $http_upgrade to compute the correct Connection value dynamically rather than hardcoding "upgrade" for every request.

🏏

Cricket analogy: It's like a stadium PA system needing three separate settings enabled together — the DRS microphone feed, the third-umpire video link, and the crowd announcement channel — before a review can actually be broadcast live to the big screen.

Setting Long-Lived Timeouts

A regular HTTP proxy connection typically finishes well within nginx's default proxy_read_timeout of 60 seconds, but a WebSocket connection is meant to sit idle between messages for minutes or hours, so leaving the default timeout in place causes nginx to silently kill quiet-but-healthy connections and force the client to reconnect. Production WebSocket proxy configurations raise proxy_read_timeout (and often proxy_send_timeout) to a much larger value, frequently paired with application-level ping/pong frames sent every 20-30 seconds so both nginx and any intermediate load balancers see steady traffic and don't mistake a quiet connection for a dead one.

🏏

Cricket analogy: It's like a rain-delay rule that assumes play resumes within an hour, but a genuine multi-hour weather delay needs a specially extended allowance or the match gets abandoned even though both teams are ready to continue.

Load Balancing WebSocket Connections

Because a WebSocket connection is a single long-lived stateful stream rather than a series of independent requests, round-robin or least_conn load balancing works fine for choosing which backend handles a given connection initially, but any application state tied to that connection (like an in-memory chat room roster) must either live in the one backend the client stuck with for the whole connection's lifetime, or be shared externally through something like Redis pub/sub. When horizontal scaling requires distributing WebSocket clients across multiple backend instances that need to broadcast to each other, ip_hash keeps a given client pinned to one backend for connection-establishment purposes, but a shared message broker is still needed for cross-instance broadcast.

🏏

Cricket analogy: It's like a single continuous innings that must be scored by one dedicated scorer for its entire duration rather than handed off mid-over between multiple scorers, even though which scorer starts the innings can be chosen freely.

The map block computing $connection_upgrade only outputs 'close' when $http_upgrade is empty (a normal HTTP request); for any non-empty Upgrade header it outputs 'upgrade', which is what actually lets the WebSocket handshake pass through nginx.

Leaving proxy_read_timeout at its 60-second default on a WebSocket location is a common production bug: connections that are healthy but quiet get killed by nginx after a minute, causing clients to reconnect constantly and masking the real fix, which is raising the timeout and adding periodic ping/pong frames.

  • WebSocket connections start as an HTTP request with Upgrade and Connection headers, switching to a persistent full-duplex stream after a 101 response.
  • nginx must be explicitly configured with proxy_http_version 1.1 and forwarded Upgrade/Connection headers to pass the handshake through.
  • A map block on $http_upgrade computes the correct Connection header dynamically, since it must differ for WebSocket vs normal requests.
  • The 60-second default proxy_read_timeout kills healthy-but-idle WebSocket connections and must be raised significantly.
  • Application-level ping/pong frames every 20-30 seconds keep intermediaries from mistaking a quiet connection for a dead one.
  • WebSocket connections are stateful for their lifetime, so cross-instance state needs an external store like Redis pub/sub.
  • ip_hash can pin WebSocket clients to a backend, but cross-instance broadcast still requires a shared message broker.

Practice what you learned

Was this page helpful?

Topics covered

#DevOps#NginxStudyNotes#ProxyingWebSocketsWithNginx#Proxying#WebSockets#Nginx#Need#StudyNotes#SkillVeris#ExamPrep