WebSockets behind Nginx need specific upgrade headers and longer timeouts. The right config: proxy_set_header Upgrade $http_upgrade, proxy_set_header Connection "upgrade", and proxy_read_timeout for long-lived connections.
Table of contents
- The default Nginx behavior
- The right config
- The TLS termination
- The keepalive
- What usually breaks
- Testing the config
- The WebSocket alternatives
- The load balancing considerations
- FAQ
The default Nginx behavior
By default, Nginx proxies HTTP requests and closes the connection after each response. WebSockets need a persistent connection; without the right headers, Nginx closes the connection after the initial HTTP upgrade, and the WebSocket falls back to long-polling or fails entirely.
The fix: tell Nginx to forward the WebSocket upgrade headers and use a longer timeout.
The right config
The right Nginx config for a WebSocket proxy:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
The four lines that matter:
proxy_http_version 1.1: WebSockets require HTTP/1.1.proxy_set_header Upgrade $http_upgrade: Forward the Upgrade header from the client.proxy_set_header Connection "upgrade": Tell the backend the connection is being upgraded.proxy_read_timeout 86400s: Allow long-lived WebSocket connections (default is 60s).
The TLS termination
The right pattern: Nginx terminates TLS, proxies plain HTTP/WebSocket to the backend.
location /ws/ {
proxy_pass http://127.0.0.1:8080;
# WebSocket-specific headers (above)
}
The client connects to wss://example.com/ws/ (TLS), Nginx decrypts and proxies to http://127.0.0.1:8080/ws/ (plain HTTP on the internal network).
The team that uses this pattern has TLS termination at the edge and unencrypted traffic on the internal network. The team that runs TLS end-to-end has the stronger security but more operational overhead.
The keepalive
WebSocket connections are long-lived. The right keepalive strategy:
- Application-level ping/pong. The server sends a ping every 30 seconds; the client responds with a pong. If no pong in 60 seconds, the connection is dead.
- TCP keepalive.
proxy_socket_keepalive on;in Nginx (newer versions) tells the kernel to send TCP keepalive packets.
The team that doesn’t keepalive has dead connections that look alive. The team that uses both application-level and TCP-level keepalive has robust connection management.
What usually breaks
The four pitfalls:
- Forgot the upgrade headers. The WebSocket fails to upgrade; the client falls back to long-polling.
- Timeout too short. Nginx closes the connection after 60 seconds; the client reconnects. The team that sees constant reconnects has a short timeout.
- Buffering issues. Nginx may buffer WebSocket frames.
proxy_buffering off;in the location block disables buffering for WebSockets. - Load balancer doesn’t support WebSockets. The team that puts an AWS ALB in front of Nginx has to enable WebSocket support on the ALB target group.
Testing the config
The right tests:
$ curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" https://example.com/ws/
Expected response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
The team that sees HTTP/1.1 101 Switching Protocols has the right config. The team that sees HTTP/1.1 200 OK has the upgrade headers missing.
The WebSocket alternatives
Beyond raw WebSockets, the alternatives:
Server-Sent Events (SSE). One-way server-to-client streaming. Simpler than WebSockets; works over HTTP. The right choice when the client only needs to receive updates from the server.
Long polling. Client makes a request; server holds the connection open until data is available. The right choice for browser compatibility with older clients.
MQTT. Lightweight pub/sub protocol. The right choice for IoT and mobile apps with unreliable networks.
gRPC streaming. Bidirectional streaming over HTTP/2. The right choice for service-to-service communication.
WebTransport. Newer HTTP/3-based transport. The right choice for new applications that want to combine WebSocket-like semantics with HTTP/3.
The team that picks the right protocol for the use case ships faster. WebSockets are not always the answer.
The load balancing considerations
WebSockets behind a load balancer:
-
Sticky sessions. Some load balancers use cookies to route WebSocket connections to the same backend. Works but limits scaling.
-
No sticky sessions. A more scalable pattern: the WebSocket server is stateless, and shared state lives in Redis or similar. Any backend can handle any connection. The right choice for production scale.
-
WebSocket-aware load balancers. HAProxy, Envoy, and AWS ALB all support WebSocket protocol natively. The team that uses these has cleaner config.
The team that uses sticky sessions has simpler code; the team that uses stateless WebSocket servers with shared state has better scaling. The choice depends on the scale requirements.
FAQ
Does Nginx support WebSockets?
Yes, since Nginx 1.3.13 (2013). The right config: the four headers listed above.
What’s the default WebSocket timeout in Nginx?
60 seconds (proxy_read_timeout). The team that has long-lived WebSockets sets a longer timeout.
Can I use a load balancer with WebSockets?
Yes, but verify support. AWS ALB supports WebSockets since 2018. GCP Load Balancer supports WebSockets. Cloudflare supports WebSockets. Older hardware load balancers may not.
How do I debug WebSocket issues?
curl -i -N -H "Connection: Upgrade" ... to test the upgrade. wscat (npm package) for interactive testing. Browser DevTools Network tab to see the WebSocket frames.
Can I use WebSockets with HTTP/2?
Not directly. HTTP/2 doesn’t include the WebSocket upgrade mechanism. The right alternatives: gRPC streaming (which works over HTTP/2) or running HTTP/1.1 for WebSocket connections alongside HTTP/2.
How many WebSocket connections can one server handle?
Depends on the server. A modern Linux server with tuned kernel parameters can handle 100K-1M concurrent WebSocket connections. The team that needs more uses multiple servers behind a load balancer.
Can I run WebSockets in serverless?
Yes, with API Gateway WebSocket API (AWS), Azure Web PubSub, or Cloudflare Durable Objects. The team that uses serverless WebSockets has zero infrastructure management but may face cost or latency trade-offs.
If you are sizing the infrastructure for the kind of project this post covers, the RunxBuild hosting calculator is the right place to model the line items. The compute, the memory, the storage, the bandwidth, the database - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers. The RunxBuild dashboard is where the team sees the actual usage in one place.
Useful related references: