# 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. 1.Check Nginx configuration:
  2. 2.```bash
  3. 3.sudo nginx -t
  4. 4.`
  5. 5.Test WebSocket with wscat:
  6. 6.```bash
  7. 7.# Install wscat
  8. 8.npm install -g wscat

# Test connection wscat -c wss://example.com/ws ```

  1. 1.Monitor logs during connection:
  2. 2.```bash
  3. 3.# Terminal 1
  4. 4.tail -f /var/log/nginx/error.log

# Terminal 2 tail -f /var/log/nginx/access.log

# Make WebSocket connection ```

  1. 1.Check backend logs:
  2. 2.```bash
  3. 3.journalctl -u websocket-backend -f
  4. 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

SymptomCauseFix
400 Bad RequestMissing Upgrade headersAdd Upgrade and Connection headers
Connection drops after 60sRead timeoutIncrease proxy_read_timeout
Connection resetBuffering enabledSet proxy_buffering off
502 Bad GatewayBackend not listeningCheck backend is running
SSL errorsWrong protocolUse wss:// for https pages
Random disconnectsLoad balancingUse ip_hash or sticky sessions
Large messages failSize limitsIncrease 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.