How to Proxy WSS WebSockets with NGINX

Overview

WebSockets allow a two-way, persistent communication channel between a client and a server. Like any other HTTP requests, WebSockets can be either secure (WSS) or insecure (WS). In This tutorial, you will be shown how to configure NGINX to proxy WSS connections.

Getting Started

If you are not a developer or have not developed a WebSocket application, you may find demo applications in Github. Each one was written for tutorials like this.

  • NodeJS Express
  • Python

Certificates

Trusted Certificates

Any certificate key-pair from a trusted source will work. Place the certificate files in a safe location that is accessible to NGINX. You will point to the key and certificate files with the nginx configuration file handling WebSocket connections.

Creating Self-Signed Certificates

Using self-signed certificates is strongly discouraged outside of testing solutions. However, since you may need a certificate to test with, the following instructions will show you how to create a key pair.

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 --nodes

Configuring Secure WebSocket Proxy

In order for WWS requests to be proxied to a backend WSS service, NGINX must be configured to listen over a secure port. Just as you would configure SSL when using NGINX to server web application, a ssl certificate and certificate key must be configured.

The following example nginx.conf adds uses a certificate file named cert.pem and a key file named key.pem. The acceptabed protocols are explicitly set using the ssl_protocols directive, and the allowed ciphers are set with the ssl_ciphers directive.

http {
  server {
    listen 443 ssl;
    server_name ws.serverlab.ca

    ssl_certificate     cert.pem;
    ssl_certificate_key key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;

      proxy_pass https://ws-backend;

      proxy_ssl_certificate     /etc/ssl/certs/cert.pem;
      proxy_ssl_certificate_key /etc/ssl/private/key.pem;

      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
  }

  upstream ws-backend {
    # enable sticky session based on IP
    ip_hash;

    server server01:3000;
    server server02:3000;
    server server03:3000;
  }
}

Connection Upgrade

The example configuration above sets the connections to Upgrade, which is how proxied connections switch to the WS and WSS protocols. The two proxy_set_header directives are what upgrade the connection.

Also, WS and WSS connections are only support on HTTP 1.1, so another directive called proxy_http_version sets the HTTP version to 1.1. Without it, a HTTP 1.0 may attempt to use WebSockets.

Upstream Servers

Upstreams can be configured with as many upstreams servers as necessary. However, if the connection is stateful then the ip_hash directory must be used.

The example has a pool of three servers named server01, server02, and server03. All of which are listening on port 3000. The default load balancing algorythm is round-robin, so new incoming connections will connect to the servers is sequence.

For example, clientA would connect to server01, clientB to server02, clientC to server03, and finally clientD to server01.

The ip_hash directive enables sticky sessions based on a client’s IP address. This ensures that all requests from an IP are handled by the same downstream server.

While this works well with clients that remain static, clients that do not remain static, such as mobile phones, may switch IP addresses. The sticky session will fail, because the client’s IP has changed and that will cause the session to be lost.

NGINX Plus allows for the creation of session cookies that are a better candidate for sticky sessions.