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:

javascript
WebSocket connection to 'wss://example.com/ws' failed: Error during WebSocket handshake: Unexpected response code: 400

Nginx error log:

bash
[error] upstream prematurely closed connection while reading response header from upstream

Connection upgrade failed:

bash
[error] no "Upgrade" header found in request

Timeout error:

bash
[error] upstream timed out (110: Connection timed out) while reading upstream

Why This Happens

  1. 1.Missing upgrade headers - WebSocket upgrade headers not configured
  2. 2.Wrong HTTP version - HTTP/1.0 doesn't support upgrade
  3. 3.Timeout too short - Connection closing prematurely
  4. 4.Buffer size issues - Large WebSocket frames truncated
  5. 5.SSL termination issues - HTTPS to WSS problems
  6. 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

CheckCommandExpected
HTTP versiongrep proxy_http_version1.1
Upgrade headergrep UpgradeSet
Connection headergrep Connectionupgrade
Timeoutsgrep timeoutSufficient
Backend workswscat directConnected
Handshakecurl headers101 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 ```

  • [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)