What's Actually Happening
Nginx fails to proxy WebSocket connections. Clients cannot establish WebSocket connections through Nginx, receiving 400 or connection closed errors.
The Error You'll See
Browser console:
WebSocket connection to 'wss://example.com/ws' failed: Error during WebSocket handshake: Unexpected response code: 400Nginx error log:
[error] upstream prematurely closed connection while reading response header from upstreamConnection upgrade failed:
[error] no "Upgrade" header found in requestTimeout error:
[error] upstream timed out (110: Connection timed out) while reading upstreamWhy This Happens
- 1.Missing upgrade headers - WebSocket upgrade headers not configured
- 2.Wrong HTTP version - HTTP/1.0 doesn't support upgrade
- 3.Timeout too short - Connection closing prematurely
- 4.Buffer size issues - Large WebSocket frames truncated
- 5.SSL termination issues - HTTPS to WSS problems
- 6.Backend not accepting WebSocket - Backend refusing connections
Step 1: Check Nginx WebSocket Configuration
```bash # Check Nginx config: cat /etc/nginx/nginx.conf cat /etc/nginx/conf.d/*.conf
# Basic WebSocket proxy config: location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
# Test config: nginx -t
# Reload config: nginx -s reload
# Check error logs: tail -f /var/log/nginx/error.log ```
Step 2: Configure Upgrade Headers
```nginx # Required WebSocket headers in Nginx:
location /ws { proxy_pass http://backend:8080;
# HTTP version must be 1.1 for WebSocket proxy_http_version 1.1;
# Upgrade headers proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Standard proxy headers 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; }
# For map to handle Connection header: # In http block: map $http_upgrade $connection_upgrade { default upgrade; '' close; }
# Then use: proxy_set_header Connection $connection_upgrade; ```
Step 3: Increase Timeout Settings
```nginx # WebSocket needs long-lived connections:
location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Increase timeouts for WebSocket proxy_connect_timeout 60s; proxy_send_timeout 3600s; # 1 hour proxy_read_timeout 3600s; # 1 hour
# Or disable timeout (use with caution): proxy_read_timeout 0; }
# Also check keepalive: keepalive_timeout 3600s; ```
Step 4: Configure Buffer Settings
```nginx # For large WebSocket messages:
location /ws { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Disable buffering for real-time WebSocket proxy_buffering off;
# Or configure buffer sizes: proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k;
# For large frames: client_max_body_size 50m; }
# Also check worker buffer: http { client_body_buffer_size 128k; } ```
Step 5: Check SSL Configuration
```nginx # For wss:// (WebSocket Secure):
server { listen 443 ssl; server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key;
location /ws { proxy_pass http://backend:8080; # Backend can be HTTP proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } }
# For HTTP to HTTPS redirect: server { listen 80; return 301 https://$host$request_uri; }
# Test SSL: openssl s_client -connect example.com:443 ```
Step 6: Test Backend WebSocket
```bash # Test backend directly (bypass Nginx): # Install wscat: npm install -g wscat
# Connect to backend: wscat -c ws://backend:8080/ws
# Check if backend accepts WebSocket: curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: test" \ http://backend:8080/ws
# Expected response: # HTTP/1.1 101 Switching Protocols # Upgrade: websocket # Connection: Upgrade
# Check backend logs: tail -f /var/log/backend.log
# Test from client: # In browser console: ws = new WebSocket('ws://backend:8080/ws'); ws.onopen = () => console.log('Connected'); ws.onerror = (e) => console.error('Error:', e); ```
Step 7: Debug WebSocket Handshake
```bash # Enable debug logging: # In nginx.conf: error_log /var/log/nginx/error.log debug;
# Reload: nginx -s reload
# Monitor logs: tail -f /var/log/nginx/error.log | grep -i "websocket|upgrade"
# Capture headers: curl -v http://example.com/ws
# Check request headers: # Must include: # Upgrade: websocket # Connection: Upgrade # Sec-WebSocket-Version: 13 # Sec-WebSocket-Key: <random>
# Check response headers: # Must include: # HTTP/1.1 101 Switching Protocols # Upgrade: websocket # Connection: Upgrade # Sec-WebSocket-Accept: <hash>
# Use tcpdump: tcpdump -i any port 80 -A -s 0 | grep -E "Upgrade|WebSocket" ```
Step 8: Fix Common Configuration Issues
```nginx # Issue 1: Using wrong HTTP version: # Wrong: proxy_http_version 1.0; # Does not support upgrade
# Fix: proxy_http_version 1.1;
# Issue 2: Missing map directive: # Add to http block: map $http_upgrade $connection_upgrade { default upgrade; '' close; }
# Issue 3: Wrong Connection header: # Wrong: proxy_set_header Connection "Upgrade"; # Case sensitive in some cases
# Fix: proxy_set_header Connection "upgrade"; # Lowercase
# Or use map: proxy_set_header Connection $connection_upgrade;
# Issue 4: Upstream doesn't match: # Check upstream definition: upstream backend { server 10.0.0.1:8080; keepalive 32; # Keepalive for WebSocket }
location /ws { proxy_pass http://backend; } ```
Step 9: Handle Multiple WebSocket Routes
```nginx # Multiple WebSocket endpoints:
# WebSocket for different paths: location /ws/chat { proxy_pass http://chat-backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
location /ws/notifications { proxy_pass http://notify-backend:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
# Single upstream with path: location /ws/ { proxy_pass http://backend:8080/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
# Use regex for flexibility: location ~ ^/ws/(.*)$ { proxy_pass http://backend:8080/$1; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ```
Step 10: WebSocket Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-websocket.sh #!/bin/bash
HOST=${1:-"localhost"} PORT=${2:-"80"} PATH=${3:-"/ws"}
echo "=== Nginx Config Check ===" nginx -t 2>&1
echo "" echo "=== WebSocket Headers ===" curl -s -I -N \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ http://$HOST:$PORT$PATH | head -20
echo "" echo "=== Backend Test ===" # Try connecting to backend directly wscat -c ws://$HOST:$PORT$PATH --timeout 5 2>&1 || echo "wscat not installed or connection failed"
echo "" echo "=== Nginx Error Logs ===" tail -20 /var/log/nginx/error.log | grep -i "websocket|upgrade|upstream"
echo "" echo "=== Active Connections ===" ss -tnp | grep :$PORT
echo "" echo "=== Nginx Process ===" ps aux | grep nginx | head -5 EOF
chmod +x /usr/local/bin/check-websocket.sh
# Usage: /usr/local/bin/check-websocket.sh example.com 443 /ws
# Monitor connections: watch -n 1 'ss -tnp | grep ESTAB | grep :443' ```
Nginx WebSocket Checklist
| Check | Command | Expected |
|---|---|---|
| HTTP version | grep proxy_http_version | 1.1 |
| Upgrade header | grep Upgrade | Set |
| Connection header | grep Connection | upgrade |
| Timeouts | grep timeout | Sufficient |
| Backend works | wscat direct | Connected |
| Handshake | curl headers | 101 response |
Verify the Fix
```bash # After fixing WebSocket
# 1. Test with wscat wscat -c wss://example.com/ws // Connected successfully
# 2. Check response headers curl -I -N -H "Upgrade: websocket" -H "Connection: Upgrade" ... // HTTP/1.1 101 Switching Protocols
# 3. Monitor Nginx logs tail -f /var/log/nginx/error.log // No WebSocket errors
# 4. Check active connections ss -tnp | grep :443 // WebSocket connections present
# 5. Test from browser # Open browser console: ws = new WebSocket('wss://example.com/ws'); ws.onopen = () => console.log('OK'); // Connected
# 6. Verify long connection # Keep connection open for minutes // Connection stays alive ```
Related Issues
- [Fix Nginx 502 Bad Gateway](/articles/fix-nginx-502-bad-gateway)
- [Fix Nginx Upstream Timed Out](/articles/fix-nginx-upstream-timed-out)
- [Fix Nginx SSL Configuration Error](/articles/fix-nginx-ssl-configuration-error)