Skip to main content

Raymii.org Raymii.org Logo

Quis custodiet ipsos custodes?
Home | About | All pages | Cluster Status | RSS Feed | Gopher

nginx 1.15.2, ssl_preread_protocol, multiplex HTTPS and SSH on the same port

Published: 06-08-2018 | Last update: 12-01-2020 | Author: Remy van Elst | Text only version of this article


Table of Contents


The NGINX blog recently had a nice article on a new feature of NGINX 1.15.2, $ssl preread protocol. This allows you to multiplex HTTPS and other SSL protocols on the same port, or as their blog states, 'to distinguish between SSL/TLS and other protocols when forwarding traffic using a TCP (stream) proxy'. This can be used to run SSH and HTTPS on the same port (or any other SSL protocol next to HTTPS). By running SSH and HTTPS on the same port, one can circumvent certain firewall restrictions. If the session looks like HTTPS, nginx will handle it, if it looks like something else, it will forward it to the configured other program. I used to use SSLH to get this functionality, but now it's built into the nginx webserver.

Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.

You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $100 credit for 60 days.

This small guide will cover the installation of the latest version of nginx on Ubuntu and configuring this multiplex feature.

You must use NGINX in proxy mode. This means that nginx will act as a load balancer or proxy in front of your application (like Django, Rails, etc).

Install the latest version of NGINX

nginx provides a repository for both CentOS, Debian/Ubuntu and SUSE. In this example we will use Ubuntu.

Download the signing key:

wget http://nginx.org/keys/nginx_signing.key

Trust the signing key:

apt-key add nginx_signing.key

Add the repository:

echo "deb http://nginx.org/packages/mainline/ubuntu/ bionic nginx" > /etc/apt/sources.list.d/nginx.list
echo "deb-src http://nginx.org/packages/mainline/ubuntu/ bionic nginx" >> /etc/apt/sources.list.d/nginx.list

Replace bionic with your version of Ubuntu (use lsb_release -a to find out).

Install nginx from the newly added repository:

apt-get update; 
apt-get install nginx

Configure nginx for ssl preread protocol

Quoted from the nginx blog:

The following configuration snippet uses the $ssl_preread_protocol variable in a map block to set the $upstream variable to the name of the upstream group appropriate for the protocol being used on the connection. The proxy_pass directive then forwards the request to the selected upstream group. Note that the ssl_preread on directive must be included in the server block for the $ssl_preread_protocol variable to work.

This piece of configuration must go in the root of your nginx config, not inside a server block.

stream {
    upstream ssh {
        server 192.0.2.10:22;
    }

    upstream https {
        server 192.0.2.20:443;
    }

    map $ssl_preread_protocol $upstream {
        default ssh;
        "TLSv1.2" https;
        "TLSv1.3" https;
        "TLSv1.1" https;
        "TLSv1.0" https;
    }

    # SSH and SSL on the same port
    server {
        listen 443;

        proxy_pass $upstream;
        ssl_preread on;
    }
}

In this case, if the protocol detected is TLSv1.2, HTTPS is assumed and the traffix is forwarded to the HTTPS server (192.0.2.20). Otherwise the traffic is forwarded to the SSH host (192.0.2.10).

SSH and HTTPS on the same server

If you want to split ssh and https on the same server, the configuration is a little bit different. You first must make sure that there is no other website listening on port :443, because that is what nginx will use for its proxy.

Not even another site withing nginx is allowed to use port 443. Change your listen blocks to use port 8443, for example:

listen [::]:8443 http2;
listen 8443 http2;

The configuration for ssh/ssl must not go in a server directive, but in the root of your nginx config:

stream {
    upstream ssh {
        server 127.0.0.1:22;
    }

    upstream https {
        server 127.0.0.1:8443;
    }

    map $ssl_preread_protocol $upstream {
        default ssh;
        "TLSv1.2" https;
        "TLSv1.3" https;
        "TLSv1.1" https;
        "TLSv1.0" https;
    }
    #
    # SSH and SSL on the same port
    server {
        listen 443;
        proxy_pass $upstream;
        ssl_preread on;
    }
}

More fun with ssl_preread

The ssl_preread module can detect more than the protocol. The SNI server name is also supported, which allows for proxy forwarding to different backend servers based on the requested SSL hostname. Quoting the documentation:

map $ssl_preread_server_name $name {
    backend.example.com      backend;
    default                  backend2;
}

upstream backend {
    server 192.168.0.1:12345;
    server 192.168.0.2:12345;
}

upstream backend2 {
    server 192.168.0.3:12345;
    server 192.168.0.4:12345;
}

server {
    listen      12346;
    proxy_pass  $name;
    ssl_preread on;
}

Do note that this also requires a newer version of nginx than by default in the Ubuntu 16.04 or 18.04 release.

Tags: firewall , folder , multiplex , nginx , proxy , security , ssh , sslh , subfolder , tutorials