The Two-Line Nginx Fix That Brings Your WebSocket Backend Online
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).
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– Replacehostwith your server's IP or hostname (e.g.,127.0.0.1) andportwith 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 theUpgradeheader.proxy_set_header Upgrade $http_upgrade– Passes the client'sUpgradeheader directly to the backend. This tells your WebSocket server that the client wants to upgrade the connection.proxy_set_header Connection "upgrade"– Overrides the defaultConnectionheader 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:
const ws = new WebSocket('ws://yoursite.com');- The client sends an HTTP GET request with
Upgrade: websocketandConnection: 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_passpoints to the exact port your WebSocket server is listening on.http://localhost:3000is a common example. - SSL requirements – Modern browsers often block insecure WebSocket connections (
ws://) from secure pages (https://). Add an SSL certificate and usewss://yoursite.comwith the correspondingproxy_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.