How to Proxy WebSockets in Nginx (Socket.io, WS, WSS)
WebSockets begin as an HTTP handshake that requests a protocol upgrade to ws:// or wss://. Nginx terminates HTTP by default and closes idle connections unless explicit Upgrade and Connection headers are configured.
Interactive Nginx Config Generator
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name example.com;
location /socket.io/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# WebSocket handshake upgrade headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Keep idle WebSockets alive for 1 day instead of default 60s
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}Directives & Architecture Explained
proxy_http_version 1.1;Required for WebSocket handshakes. The default Nginx proxy protocol is HTTP 1.0 which does not support persistent multiplexing.
proxy_set_header Upgrade $http_upgrade;Passes the client upgrade token ("websocket") to the upstream application.
proxy_set_header Connection $connection_upgrade;Translates connection header dynamically using map to ensure clean close when no upgrade is requested.
proxy_read_timeout 86400s;Prevents Nginx from killing inactive WebSocket connections after the default 60-second idle timeout.
Production Verification & Reload Workflow
sudo nginx -tCheck configuration and map block syntax
wscat -c ws://ws.example.com/socket.io/?EIO=4&transport=websocketConnect using wscat CLI to verify instant protocol upgrade and echo responses
Production Troubleshooting Tips
- β’The map $http_upgrade directive MUST be placed inside the http {} block, outside server {}.
- β’If connections disconnect exactly after 60 seconds, verify that proxy_read_timeout has been increased.
- β’For SSL WebSockets (wss://), ensure the server block listens on 443 ssl and has valid TLS certificates.
Frequently Asked Questions
Why does Nginx disconnect WebSockets after 60 seconds?
The default proxy_read_timeout is 60 seconds. If neither the client nor server sends a ping/pong frame within that window, Nginx terminates the TCP socket.
What does the map block achieve?
It sets $connection_upgrade to "upgrade" when the Upgrade header is present, and "close" when it is empty, preserving HTTP connection standards.