Skip to main content

NGINX as a Proxy for Websockets

Shenzhen, China

NGINX supports WebSocket by allowing a tunnel to be set up between a client and a backend server. For NGINX to send the Upgrade request from the client to the backend server, the Upgrade and Connection headers must be set explicitly, as in this example:

location /wsapp/ {
    proxy_pass http://wsbackend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
}

Building the Websocket Server

Initialize the Project

Initialize your Node.js App and install the websocket package using npm:

npm init -y
npm install ws

Websocket Application

Create the server configuration:

./server.js

port = 8080
var Msg = '';
var WebSocketServer = require('ws').Server
    , wss = new WebSocketServer({port});
    wss.on('connection', function(ws) {
        ws.on('message', function(message) {
        console.log('Received from client: %s', message);
        ws.send('Server received from client: ' + message);
    });
 });
console.log("Websocket Server started on port " + port);

And execute the server with:

node server.js

The server prints an initial Websocket Server started on port 8080 message and then listens on port 8080, waiting for a client to connect to it. When it receives a client request, it echoes it and sends a message back to the client containing the message it received.

NGINX Configuration

To have NGINX proxy the requests to our websocket app on the server IP 192.168.2.111, I create the following configuration:

/opt/websocket/wsserver/nginx.conf

worker_processes 1;
worker_rlimit_nofile 8192;

events {
  worker_connections  1024;
}

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
 
    upstream websocket {
        server 192.168.2.111:8080;
    }
 
    server {
        listen 8300;
        location / {
            proxy_pass http://websocket;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $host;
        }
    }
}

The map block sets the Connection header to close when the Upgrade header in the request is set to ''. NGINX listens on port 8300 and proxies requests to the backend WebSocket server on port 8080. The proxy_set_header directives enable NGINX to properly handle the WebSocket protocol.

Docker

I am going to use the NGINX docker image to spawn a container with the configuration above:

docker pull nginx:1.21-alpine
docker run -d --name nginx --network=host -v /opt/websocket/wsserver/nginx.conf:/etc/nginx/nginx.conf:ro nginx:1.21-alpine 

WS Client

I can now install a websocket client called wscat. I can use the program to connect to the server:

npm install wscat -g

wscat --connect ws://192.168.2.111:8300
Connected (press CTRL+C to quit)
> Konbanwa!
< Server received from client: Konbanwa!
node server.js
Websocket Server started on Port 8080
Received from client: Konbanwa!

wscat connects to the WebSocket server through the NGINX proxy. When you type a message for wscat to send to the server, you see it echoed on the server and then a message from the server appears on the client.

Adding Encryption

I already ran into some issues with HAProxy trying to add a self-signed TLS certificate. I will get back to this later (when I can set this up on server that is accessible via domain / CA cert)