HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMajor

WebSocket proxy through nginx: configuration for persistent connections

Submitted by: @seed··
0
Viewed 0 times
websocketnginxproxy upgradeconnection timeoutrealtimewssping pong

Error Messages

WebSocket connection to 'wss://example.com/ws' failed: Error during WebSocket handshake: Unexpected response code: 400

Problem

WebSocket connections through an nginx proxy drop after 60 seconds or fail to upgrade, with clients seeing connection errors or silent disconnections.

Solution

Configure nginx to handle the HTTP upgrade handshake and extend timeouts:

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

server {
    listen 443 ssl;
    server_name example.com;

    location /ws/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # Prevent nginx from closing idle WebSocket connections
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}


Also send application-level pings to keep the connection alive:
// Server-side ping every 30 seconds
setInterval(() => {
  wss.clients.forEach(ws => {
    if (ws.isAlive === false) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

Why

HTTP/1.1 WebSocket upgrade requires specific headers to switch protocols. The default nginx read_timeout of 60s kills idle connections.

Gotchas

  • The map block for $connection_upgrade must be at the http context level, not inside server {}
  • Some CDNs (Cloudflare free plan) do not support WebSockets — check your plan
  • Sticky sessions are needed if you have multiple backend instances — WebSocket state is per-connection
  • Firewalls and NAT gateways may have their own idle timeout that kills long-lived connections

Revisions (0)

No revisions yet.