Nginx WebSockets: Diagnose Before and After HTTP 101

When WebSocket connection fails, first determine whether the HTTP handshake reached 101. A 401, 403, 404 or 502 before that point needs different evidence from a connection that closes later. Do not change CORS settings based only on one browser-console message.

The forwarding difference observed in the fixture

A separate Nginx 1.24.0 instance proxied to a loopback mock that returned 101 when Upgrade was websocket and 426 otherwise. The same request produced 101 in the location that explicitly forwarded the headers and 426 in the one that did not. This tests header forwarding, not a complete WebSocket frame exchange.

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
# Inside the intended proxy location:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

The map belongs in the http context; the remaining fragment belongs in the intended proxy location. Actual upstream, authentication and TLS settings are still required. The Nginx WebSocket documentation explains explicit forwarding of hop-by-hop headers. Disabling Origin validation is not a fix for this forwarding problem.

After 101, inspect connection lifetime and frames

Even after 101, application messages and reconnection remain separate checks. For regularly timed disconnects, correlate proxy read timeout, application ping/pong and intermediate idle-connection policies. proxy_read_timeout concerns the interval between successive reads, not the total session lifetime. Verify that heartbeats actually traverse the path before simply increasing it.

Preserve the previous configuration, check syntax and reconnect through the intended location. Success means a handshake, an actual message round trip and survival beyond the relevant idle interval. The observed 101 alone does not establish recovery for mobile background behavior, Tailscale paths or a real application.