Nginx WebSocket Proxying and Idle Timeout Behavior
Quick answer
To proxy WebSockets through Nginx, use HTTP/1.1 and pass the Upgrade and Connection headers to the backend. Connections are closed when idle longer than...
To proxy WebSockets through Nginx, use HTTP/1.1 and pass the Upgrade and Connection headers to the backend. Connections are closed when idle longer than proxy_read_timeout, which defaults to 60 seconds, so long-lived sockets need either a larger timeout or regular heartbeat messages. Every proxy and load balancer in the path has its own idle timeout, and the shortest one wins.
What Configuration Does WebSocket Proxying Need?
WebSocket connections begin as a normal HTTP request that asks to switch protocols. By default, Nginx does not forward the headers that make this possible, so the handshake fails. You must tell Nginx to use HTTP/1.1 toward the backend and forward the upgrade request.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /ws/ {
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;
}
}
The map block makes the Connection header correct for both WebSocket and regular requests. Place the map in the http context, outside of server blocks. Also pass the original host and client address headers if your application needs them.
Why Do WebSocket Connections Drop When Idle?
Nginx treats the proxied connection like any other. If no data is read from the backend for longer than proxy_read_timeout, it closes the connection. For a chat or notification socket that is quiet for several minutes, this default of 60 seconds ends it unexpectedly.
Related timeouts apply too. The proxy_send_timeout limits gaps between writes toward the backend. Timeouts at other layers can be even shorter. Cloud load balancers, CDNs, corporate proxies, and mobile networks often close idle connections after tens of seconds or a few minutes, regardless of what Nginx allows.
- Check the idle timeout of every component between the client and the application.
- Remember that raising only the Nginx value does not help if another layer closes first.
- Documentation for load balancers and CDNs usually states their idle limits and whether WebSockets are supported.
How Do Heartbeats Solve the Problem?
Instead of setting enormous timeouts, send small periodic messages so the connection is never idle. The WebSocket protocol defines ping and pong frames for this purpose, and many libraries can send them automatically on an interval.
A heartbeat interval shorter than the shortest idle timeout in the path keeps the connection open. A common choice is somewhere between 20 and 30 seconds, but base it on the actual limits of your infrastructure. Heartbeats also help detect dead connections, such as clients that vanished without closing the socket, so the server can free resources.
- Have the server send pings, and close connections that fail to answer within a deadline.
- Implement reconnection with exponential backoff and jitter on the client.
- Do not rely on long timeouts alone, since they hold resources for dead clients.
- Application-level heartbeat messages work when browsers do not expose ping frames to scripts.
What Else Should You Plan for in Production?
WebSockets are long-lived, so they affect deployment and scaling. Each open connection holds a file descriptor and some memory in Nginx and in your application. Check worker connection limits and file descriptor limits, and remember that a proxied WebSocket uses two connections: one from the client and one to the backend.
During deployments, reloading Nginx lets old workers keep serving existing connections until they close, which can leave old workers alive for a long time with long-lived sockets. Plan for this with a shutdown timeout, or by asking clients to reconnect gracefully.
Finally, secure the endpoint. Authenticate during the handshake, validate the Origin header to limit which sites can open connections, use TLS, and apply limits on connections per client to resist abuse.
Frequently Asked Questions
Why does my WebSocket handshake fail behind Nginx?
Usually the Upgrade and Connection headers are not forwarded, or HTTP/1.1 is not enabled for the proxied connection.
What is the default timeout that closes idle WebSockets?
The proxy_read_timeout default of 60 seconds. Heartbeats or a larger value prevent the drop.
Do I need sticky sessions for WebSockets?
Not for the connection itself, since it stays on one backend. You may need shared state so any server can handle any user.
Can I use Nginx and a cloud load balancer together?
Yes, but align timeouts and verify WebSocket support in both layers.
Summary
Forward the upgrade headers with HTTP/1.1, set timeouts deliberately, and use heartbeats to keep connections alive and detect dead ones. Check every layer's idle limits and the Nginx documentation for your version.