WebSocket Reverse Proxy

Introduction

WebSockets keep a long-lived connection for chat, live updates, and Socket.IO. Nginx must upgrade HTTP to WebSocket and proxy the stream to Node or other backends. This chapter adds the required headers to an existing proxy_pass setup and notes Socket.IO-specific paths.

Prerequisites

HTTP Upgrade Flow

text
  Client                    Nginx                  Backend
     │  GET + Upgrade         │                        │
     ├───────────────────────►│  GET + Upgrade         │
     │                        ├───────────────────────►│
     │◄───────────────────────┤  101 Switching         │
     │  101 Switching         │◄───────────────────────┤
     │  (bidirectional)       │  (proxied stream)      │

Normal REST proxy_pass without upgrade headers often fails WebSocket handshakes with 400 or immediate disconnect.

Basic WebSocket location

Code explanation:

  • Upgrade / Connection — required for protocol switch to WebSocket
  • Long timeouts — idle connections stay open (86400s = 24h example; tune per app)

Reload and test with browser DevTools or wscat:

bash
npx wscat -c ws://example.com/ws/

Use wss:// when TLS is enabled on Nginx.

Map-Based Connection Header (Multiple Routes)

When mixing normal HTTP and WebSocket on same server:

Code explanation:

  • Non-upgrade requests get Connection: close
  • Upgrade requests get Connection: upgrade

Socket.IO

Socket.IO uses HTTP polling fallback and path /socket.io/:

nginx
location /socket.io/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    include /etc/nginx/proxy_params;
    proxy_read_timeout 86400s;
}

Ensure Node Socket.IO server path matches. Sticky sessions may help if multiple Node instances—see load balancing chapter.

HTTPS and WebSocket

Client connects to wss:// on port 443. Nginx terminates TLS; upstream stays http://127.0.0.1:3000. No separate SSL on Node for that hop on same server.

Common Failures

SymptomFix
502 after upgradeBackend not supporting WS; wrong port
Immediate disconnectMissing Upgrade headers; timeout too low
Works locally not through NginxPath prefix strip—align location and proxy_pass
400 Bad RequestHTTP/1.0 to upstream—set proxy_http_version 1.1

Check error log during connect attempt.

FAQ

Does gzip affect WebSocket?

No—upgrade bypasses normal response body handling.

Spring Boot WebSocket?

Same headers on proxy_pass to Tomcat/WebSocket endpoint—often /ws or STOMP path.

HTTP/2 and WebSocket?

Browsers use HTTP/1.1 or HTTP/2 for initial request; upgrade handled per spec—Nginx config above still applies at location level.

Buffering?

proxy_buffering off; sometimes needed for streaming—test if messages delay.

Multiple WS backends?

upstream with ip_hash for stateful connections—stateless WS can round-robin.

Cloudflare in front?

WebSockets must be enabled on CDN orange-cloud settings.