Back to Blog
DevOpsJuly 7, 20264 min read

Nginx WebSocket Proxy: Fix Connection Timeouts and Upgrades

Master nginx websocket proxy configuration. Learn to handle the upgrade header, fix connection timeouts, and keep your real-time traffic stable.

nginxwebsocketsdevopsproxyweb-servers

Getting a real-time application working behind Nginx can be frustrating. You’ll often find your WebSocket connections dropping after exactly 60 seconds, or worse, failing to handshake entirely. I’ve spent more than a few late nights debugging these issues, and it usually comes down to how Nginx handles the transition from HTTP to a persistent binary stream.

If you’re already familiar with the basics, you might have read Nginx as a reverse proxy: The config explained line by line, but WebSockets require a slightly different approach than standard REST APIs.

The Problem: Protocol Upgrades

The core of the issue is that WebSockets start as a standard HTTP/1.1 request that includes an Upgrade header. If Nginx doesn't explicitly pass this header—along with the Connection header—to your backend, the upgrade handshake fails. Your client will see a 400 Bad Request or a simple connection reset.

To fix this, you need to explicitly map the connection type. Here is the configuration block I use in production:

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

    server {
        location /ws/ {
            proxy_pass http://backend_cluster;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $host;
        }
    }
}

The map directive here is crucial. It ensures that if the client sends an Upgrade request, Nginx passes it through. If they don't, it defaults to closing the connection, which is exactly what you want for standard traffic.

Solving the WebSocket Connection Timeout

Even with the headers configured, you might see connections dropping silently after about 60 seconds. This happens because Nginx has a default proxy_read_timeout of 60 seconds. If your WebSocket is idle for that long, Nginx assumes the connection is dead and kills the socket.

To keep your connections alive, you need to extend the timeout and ensure Nginx doesn't aggressively close idle streams.

SettingDefaultRecommended for WebSockets
proxy_read_timeout60s3600s (or more)
proxy_send_timeout60s3600s
keepalive_timeout75s65s

I typically set both proxy_read_timeout and proxy_send_timeout to 3600 seconds (one hour). This is usually enough to cover most user sessions without leaving ghost connections open indefinitely. If you need help hardening your infrastructure, I often assist with VPS Server Setup, Deployment & Hardening to ensure these timeouts and security headers are correctly tuned.

Dealing with Connection Keepalive

Simply increasing the timeout isn't always enough if you have a high volume of traffic. You should also consider enabling Nginx connection keepalive to the upstream backend. This reduces the overhead of re-establishing TCP connections for every request.

Add this to your upstream block:

NGINX
upstream backend_cluster {
    server 127.0.0.1:8080;
    keepalive 32;
}

By keeping a pool of connections open to your application server, you’ll notice a significant drop in latency for new handshake requests. Just remember that the keepalive value here refers to the number of idle connections kept in the cache, not the total number of concurrent connections.

Common Pitfalls

We once tried to use a blanket proxy_set_header Connection "upgrade" without the map directive. It broke our REST API endpoints because the backend expected a standard keep-alive header for those requests. Always use the map directive I showed above to keep your configuration clean and context-aware.

Another thing to watch for is the proxy_buffering directive. For WebSockets, Nginx should not buffer the output. By default, it usually handles this fine when the upgrade header is present, but if you’re seeing strange latency spikes, verify that proxy_buffering off; is set within your location block.

Frequently Asked Questions

Why does my Nginx WebSocket proxy return 400 Bad Request? It usually means the Upgrade or Connection headers are missing or malformed. Ensure you are passing proxy_http_version 1.1 and mapping the connection header correctly.

Do I need to change my SSL/TLS settings? Generally, no. If you're using WSS (WebSocket Secure), Nginx handles the decryption before passing the traffic to the backend, so the backend still sees it as a standard WebSocket upgrade request.

How do I verify the connection is actually working? Use curl -i -H "Upgrade: websocket" -H "Connection: Upgrade" http://your-domain/ws/. If your config is correct, you should see a 101 Switching Protocols response.

I’m still experimenting with how Nginx handles massive concurrent WebSocket connections under heavy load, specifically regarding memory allocation for worker processes. For now, the configuration above is my go-to baseline. It’s simple, effective, and hasn’t let me down yet.

Similar Posts