Nginx proxy based on SNI without decryption

Solution 1:

It looks like this is now supported using the ngx_stream_ssl_preread_module module

Solution 2:

NO, you can't do with Nginx. By default, Nginx is always decrypting content, so Nginx can apply request routing. Some solution that can be tried:

  • There are 3rd party module called nginx_tcp_proxy_module. I haven't tried it yet. Because that module do proxy on network layer, so it will passing request without decryption.

  • The preferred solution is use HAProxy. This tutorial suggest that you can do TCP proxy with SNI capabilities.


Sidenote

By default, Nginx always act as SSL offloading/decryption process on proxy. Here some the advantages doing SSL offloading (taken from here)

  • Improved performance
  • Better utilization of the backend servers
  • Intelligent routing
  • Certificate management
  • Security patches

Solution 3:

There are some things you have to note about doing this:

  • You're going to have to use a stream block rather than an http block, which means:
  • You can't set error_page directives because HTTP error codes are at the application layer and you're not decrypting that layer
  • You can't proxy_pass to an http:// server because you're not doing SSL termination any more
  • You can't proxy_set_header because you can't inject plaintext into an encrypted stream

If you accept those limitations and e.g. you're going to do the work in another server that is either the final destination or an intermediate proxy that is doing SSL termination, then here's what you need to do:

  1. First, note that most nginx server configurations automatically wrap everything in an http block. So if e.g. you're used to just dropping a file into /etc/nginx/conf.d on Red Hat-type installs or /etc/nginx/sites-available with a symlink in /etc/nginx/sites-enabled on Debian-type installs, that's not going to work here. You're going to have to make at least some minor changes to the master configuration file /etc/nginx.conf, if only to create a stream block that includes other files like this:

    stream {
        include /etc/nginx/stream.d/*.conf;
    }
    
  2. Next, you need to create a stream server. You're limited to the directives that are in the ngx_stream_* modules, which excludes all the HTTP-specific stuff. As Robert Wagner's answer pointed out, you'll need to use the ngx_stream_ssl_preread module which doesn't exist in nginx versions older than 1.11.5. Create a .conf file in /etc/nginx/stream.d or whatever other directory you chose, and define your severs. The following is taken verbatim from what's in the module documentation, and gives an example of SNI-based filtering (there's also ALPN and SSL version filtering available):

    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;
    }
    
  3. Now, you have a server that doesn't decrypt traffic but does determine where to send it based on SNI. If a client connects and requests backend.example.com then it will be routed to backend and otherwise it will be routed to backend2.

There are other things you may want to consider as well, like setting tcp_nodelay or defining a stream log_format and enabling access_log for the stream block, but of course the format and variables will be different from an HTTP access log.

Tags:

Nginx