WebSockets start as an HTTP request with Upgrade: websocket, then switch protocols. Nginx only forwards that upgrade if you explicitly pass the Upgrade and Connection headers and use HTTP/1.1. The clean way is a map so the Connection header is upgrade for WebSocket requests and close for everything else.
Put the map at the http { } level (e.g. in nginx.conf or a file in conf.d/):
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Then in the server { }:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
# WebSockets are long-lived; don't cut idle ones at the default 60s.
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
Notes:
proxy_http_version 1.1is required — the default 1.0 has noUpgrademechanism.- The
mapavoids hard-codingConnection "upgrade", which would break plain HTTP requests sharing the same location. - If only one path is a socket (e.g. Socket.IO’s
/socket.io/), put these directives in that specificlocation /socket.io/ { … }and proxy the rest normally. - Long
proxy_read_timeoutkeeps idle connections alive; pair it with application-level pings if your client doesn’t send keep-alives.
sudo nginx -t && sudo nginx -s reload