shahriyar.dev
Back to blog
nginxwebsocketreverse-proxyproxy-configurationwebsocket-proxy

The Two-Line Nginx Fix That Brings Your WebSocket Backend Online

·3 min read

Let's start with the concept: you've set up a WebSocket server locally, tested it, and it works fine on localhost. But when you try to connect from an external client—say, a production frontend or a remote application—you get connection errors. The issue isn't your WebSocket logic; it's your Nginx reverse proxy configuration.

Out of the box, Nginx treats WebSocket connections as standard HTTP requests. This causes the upgrade handshake to fail, and your client is left hanging with a 400 or 101 error. The fix is a small but critical configuration change.

The Essential Nginx WebSocket Proxy Configuration

Below is the configuration that makes external WebSocket connections work reliably. Place this inside the server block of your Nginx site configuration (typically in /etc/nginx/sites-available/your-site or /etc/nginx/nginx.conf).

nginx
server {
    listen 80;
    server_name yoursite.com;
    
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://host:port;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";  
    }
}

What each directive does

  • proxy_pass http://host:port – Replace host with your server's IP or hostname (e.g., 127.0.0.1) and port with your WebSocket server's port (e.g., 3000). This is where Nginx forwards incoming traffic.
  • proxy_http_version 1.1 – Forces the proxy to use HTTP/1.1. WebSocket upgrades rely on HTTP/1.1 semantics; HTTP/1.0 does not support the Upgrade header.
  • proxy_set_header Upgrade $http_upgrade – Passes the client's Upgrade header directly to the backend. This tells your WebSocket server that the client wants to upgrade the connection.
  • proxy_set_header Connection "upgrade" – Overrides the default Connection header to explicitly signal an upgrade request. Without this, Nginx may strip or modify the header, breaking the handshake.

Testing the connection from outside

Once Nginx is reloaded, your WebSocket endpoint becomes accessible at ws://yoursite.com (or wss://yoursite.com if you add SSL). Your client code connects like this:

javascript
const ws = new WebSocket('ws://yoursite.com');
  • The client sends an HTTP GET request with Upgrade: websocket and Connection: Upgrade.
  • Nginx intercepts the request, passes it to your backend with the correct headers.
  • The backend responds with a 101 Switching Protocols status, completing the handshake.
  • The connection is now a persistent, full-duplex WebSocket tunnel.

Troubleshooting common issues

  • Port mismatch – Ensure proxy_pass points to the exact port your WebSocket server is listening on. http://localhost:3000 is a common example.
  • SSL requirements – Modern browsers often block insecure WebSocket connections (ws://) from secure pages (https://). Add an SSL certificate and use wss://yoursite.com with the corresponding proxy_pass https://... and SSL headers.
  • Firewall rules – External clients must be able to reach Nginx on port 80 or 443. Verify no firewall is blocking those ports.
  • CORS issues – If your frontend is on a different origin, your backend must include CORS headers for WebSocket. This is handled at the application layer, not in Nginx.

Closing thoughts

A WebSocket proxy with Nginx is elegantly simple once you understand the upgrade handshake. These few lines of configuration turn a local-only WebSocket into a production-ready, externally accessible service. That's the difference between a prototype and a deployed application.

If you run into edge cases, remember that the Upgrade and Connection headers are the heart of the handshake. Every proxy configuration is built around getting those headers to your backend intact. Once they arrive correctly, your WebSocket will work through any Nginx instance.

Comments