# Nginx WebSocket Connection Failed
WebSocket connections through Nginx fail with 400 Bad Request, immediate disconnection, or silent failure. The WebSocket protocol requires specific headers to be passed through, and standard HTTP proxy configuration doesn't work. Real-time features like chat, notifications, and live updates break.
Understanding WebSocket Proxying
WebSockets start as HTTP requests with an Upgrade: websocket header. The server must respond with 101 Switching Protocols to upgrade the connection. Nginx needs explicit configuration to pass these headers and maintain the persistent connection.
Check the Nginx error log:
``bash
tail -f /var/log/nginx/error.log
Browser console errors:
``
WebSocket connection to 'wss://example.com/ws' failed: Error during WebSocket handshake: Unexpected response code: 400
WebSocket connection to 'wss://example.com/ws' failed: Error during WebSocket handshake: net::ERR_CONNECTION_RESET
Common Cause 1: Missing Upgrade Headers
The most common issue - Nginx doesn't pass the upgrade headers.
Problematic config:
``nginx
location /ws {
proxy_pass http://backend:8080;
}
This treats WebSocket as regular HTTP.
Solution: Add WebSocket headers:
``nginx
location /ws {
proxy_pass http://backend:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
For socket.io specifically:
``nginx
location /socket.io/ {
proxy_pass http://backend: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;
}
Common Cause 2: Connection Header Issues
The Connection header needs special handling. Direct string values can cause problems.
Map directive approach (recommended): ```nginx # In http block (outside server) map $http_upgrade $connection_upgrade { default upgrade; '' close; }
server { location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } } ```
This sends upgrade when the client requests upgrade, and close otherwise.
Common Cause 3: Timeout Disconnects
WebSockets are long-lived connections. Default timeouts kill them.
Problematic config:
``nginx
location /ws {
proxy_pass http://backend:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Default proxy_read_timeout is 60s - connection dies
}
Solution: Increase timeouts: ```nginx location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Long timeout for persistent connections proxy_read_timeout 3600s; proxy_send_timeout 3600s;
# Disable buffering for real-time proxy_buffering off; } ```
Common Cause 4: WSS (Secure WebSocket) Issues
Secure WebSockets (wss://) require SSL termination configuration.
Nginx handles SSL, proxies to ws:// backend: ```nginx server { listen 443 ssl; server_name example.com;
ssl_certificate /etc/ssl/certs/fullchain.pem; ssl_certificate_key /etc/ssl/certs/privkey.pem;
location /ws { proxy_pass http://backend:8080; # http, not https 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_set_header X-Forwarded-Proto https; # Tell backend it's https } } ```
If backend also uses SSL: ```nginx location /ws { proxy_pass https://backend:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Trust backend's certificate (if self-signed) proxy_ssl_verify off; } ```
Common Cause 5: Buffering Issues
Buffering causes delays in real-time message delivery.
Problem:
``nginx
# Default: proxy_buffering on
Solution: Disable buffering: ```nginx location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_buffering off; proxy_cache off; } ```
Common Cause 6: Large Message Frames
Default limits may truncate large WebSocket messages.
Solution: Increase limits: ```nginx location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Large message support client_max_body_size 100m; proxy_read_timeout 3600s; } ```
Common Cause 7: Wrong Protocol (ws vs wss)
Client-side connection to wrong protocol.
Client code issue: ```javascript // Wrong - using ws on https page const ws = new WebSocket('ws://example.com/ws');
// Correct - match protocol
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(${protocol}//example.com/ws);
```
Or hardcode for production:
``javascript
const ws = new WebSocket('wss://example.com/ws');
Common Cause 8: Sticky Sessions for Load Balancing
WebSockets need to maintain connection to same backend.
Problem: ```nginx upstream backend { server backend1:8080; server backend2:8080; }
location /ws { proxy_pass http://backend; # Requests go to random backend - WebSocket state lost } ```
Solution: Use IP hash: ```nginx upstream backend { ip_hash; server backend1:8080; server backend2:8080; }
location /ws { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ```
Or use consistent hashing:
``nginx
upstream backend {
hash $remote_addr consistent;
server backend1:8080;
server backend2:8080;
}
Common Cause 9: Backend Not Handling WebSocket
The backend application might not be configured for WebSocket.
Diagnosis:
``bash
# Test backend directly
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" http://backend:8080/ws
Expected response:
``
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ...
If you get 400, 404, or no 101, the backend isn't handling WebSocket.
Verification Steps
- 1.Check Nginx configuration:
- 2.```bash
- 3.sudo nginx -t
- 4.
` - 5.Test WebSocket with wscat:
- 6.```bash
- 7.# Install wscat
- 8.npm install -g wscat
# Test connection wscat -c wss://example.com/ws ```
- 1.Monitor logs during connection:
- 2.```bash
- 3.# Terminal 1
- 4.tail -f /var/log/nginx/error.log
# Terminal 2 tail -f /var/log/nginx/access.log
# Make WebSocket connection ```
- 1.Check backend logs:
- 2.```bash
- 3.journalctl -u websocket-backend -f
- 4.
`
Complete Working Configuration
```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; }
upstream websocket_backend { ip_hash; server 127.0.0.1:8080; keepalive 32; }
server { listen 80; server_name example.com;
location /ws { proxy_pass http://websocket_backend; 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; proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_buffering off; } } ```
Quick Reference
| Symptom | Cause | Fix |
|---|---|---|
| 400 Bad Request | Missing Upgrade headers | Add Upgrade and Connection headers |
| Connection drops after 60s | Read timeout | Increase proxy_read_timeout |
| Connection reset | Buffering enabled | Set proxy_buffering off |
| 502 Bad Gateway | Backend not listening | Check backend is running |
| SSL errors | Wrong protocol | Use wss:// for https pages |
| Random disconnects | Load balancing | Use ip_hash or sticky sessions |
| Large messages fail | Size limits | Increase client_max_body_size |
WebSocket proxying requires explicit header handling and timeout configuration. The Upgrade and Connection headers are non-negotiable, and timeouts must match your application's needs.