Nginx SSL vhosting using Server Name Indication

Here is the issue: I have a tcp/443 DNAT to a specific machine running some specific HTTPS app that does not work behind a reverse proxy.

Obviously, I want to run others application on 443 and I’m not allowed to get any other port.

Sounds pretty bad, right ?
Actually, there’s a way out and it’s called “nginx-is-so-fuckin-powerfull” 😉

As you may know, a long time ago a feature has been added to TLS which is called “Server Name Indication”. Before this it was impossible to serve multiple virtual hosts on a single address because SSL session was negociated before the client actually sends the requested vhost name.

With SNI, there’s a quick chat between your HTTPS server and the remote browser, something like:

Ok that’s probably not really accurate but who cares about what exactly happens. The thing is: there’s a routing capability before serving the SSL certificate and we know the requested domain name at this point; and guess what: NGINX offers routing possibility using SNI name !!

First thing… You need a really really new NGINX version (1.11.5), but if your distro doesn’t have it you can use NGINX repositories.
Second, you must understand that very old clients may not use SNI. If it doesn’t it will hit the NGINX default vhost. So make sure to keep the old behavior as default, just in case.
Here is the client compatibility list for SNI: https://en.wikipedia.org/wiki/Server_Name_Indication
I leave it to you to decide if you care about handling Internet Explorer < 7. So let's configure NGINX correctly: You need to define a stream {} section on nginx.conf top, just like the http one.

Of course, you need to disable default http/server to listen on port 443 (comment lines like "listen 443 ssl" in all your existing configuration). Now we'll create a stream server, which is a plain TCP proxy: In /etc/nginx/stream.conf.d/443.conf:

And that's it 😀 You can now create a new http/server instance on port 8443 to serve your different new https vhosts but I suggest starting with the default virtual host (/etc/nginx/conf.d/default.conf) by adding "listen 8443 ssl default_server" and some ssl cert and key directives. Here is a example of the stream_443.log:

Nice work NGINX, as usual ! Going further: There's just a little issue here: The real HTTPS on port 8443 will always see incoming IP address as 127.0.0.1. Howerver, there's an overhead called "proxy_protocol" that can help passing proxying related things between NGINX servers but my equipment running behind doesn't like this. So the idea here is to use proxy_protocol between my stream/443 and http/8443 instances and strip it when proxying to original_dest using a dummy stream server that does nothing else that popping out the proxy_protocol data and forwarding to the real server. Then I will restore remote_addr in http/8443. The new config file is now:

In the http/8443 vhost, we set the following to restore original client IP address:

Nginx -_- Bonus stuff: I case you're having issue with SELinux (and you will, for instance it will deny NGINX to start a connection from port 8080 to a remote host), you can use the following to extract failures from audit.log and turn them into a permanent SELinux exception

11 thoughts on “Nginx SSL vhosting using Server Name Indication

  1. Hi I wonder if you can help me, I’m looking for a config to do Reverse Proxy SSL passthrough, I have scoured the web and tried Haproxy, but I get ssl errors with that and I don’t find it reliable, so I want to do it with nginx.
    I have 3 webservers, each serving a unique website with their own certs and domain names, but I only have 1 WAN ip. so far it works perfectly because all 3 sites are running on 1 the same server, I want to split them and run them each on their own server. Virtual hosts work on nginx because they are on 1 server.
    The basic config is as follows, for example all traffic is forced to SSL, so http://www.example.com will redirect to https://www.example.com, all of this is working perfectly on all sites. however, I want to make a landing server (for example webserver1 192.168.1.20) that would listen to all 80 and 443 traffic, only do the routing&proxying and then depending on the domain name route them, http://www.example.com to (webserver2 192.168.1.21), blog.example.com to (webserver3 192.168.1.22), forum.example.com to (webserver4 192.168.1.23) etc, these are just examples, but all the sites are totally different and unique from each other.
    I have tried different setups and config’s with no success. I have tried this one https://serversforhackers.com/tcp-load-balancing-with-nginx-ssl-pass-thru but I can’t use the ‘server_name’ directive with it. I have tried haproxy but I get ssl errorrs all the time and timeouts, dropped connections with it. etc. I found this tutorial/config of yours so far the best option, but I cant get it to work the way I want to.

    Kind regards,
    Dion Beukes

  2. Hello,

    I don’t really understand what you are trying to do but if you’re doing HTTP(S) onl there’s no point using TCP proxying.

    I would strongly suggest moving all the SSL stuff on the main server, Internet accessible and do simple HTTP proxying for each virtual host.

    Regards, Adam.

  3. well it does, when you want to serve websites on port 443 AND want to server some other (non-http traffic) server on the same port 443. like a VPN server or a stun/turn server

  4. i actually tried your setup last night bu i ran into the following:

    my currently running setup:
    nginx 1.10.3, and the stream module is in the module folder so i assume its compiled with it.

    i have 5 sites running as vhosts all on port 80&443
    now i want to run a turn server on port 443 instead of the default port, but i only have one public ip, so thats a challenge.

    i created the 443.conf like yours. changed all the 5 sites to run on 8443.

    when i try to load nginx it fails:
    nginx: [emerg] “map” directive is not allowed here in /etc/nginx/stream.conf.d/443.conf:1

    when i delete the maps part i get the:
    [emerg] “log_format” directive is not allowed here in /etc/nginx/stream.conf.d/443.conf:15

    i don’t understand why, and where does it want me to place those parts of the config.

    any idea?

  5. Hello,

    Can you show me where you included this 443.conf file? It must be at the top of the nginx config, outside any server block…

    Regards

    • cat /etc/nginx/nginx.conf

      user www-data;
      worker_processes auto;
      pid /run/nginx.pid;
      include /etc/nginx/modules-enabled/*.conf;

      events {
      worker_connections 768;
      # multi_accept on;
      }

      stream {
      include /etc/nginx/stream.conf.d/*.conf;
      }

      http {

      ##
      # Basic Settings …..
      ….
      ….

  6. Awesome post, I finally managed to get nginx to handle both incoming OpenVPN traffic as well as a bunch of websites on different URLs. Previously I was relying on OpenVPN’s ability to pass through non-VPN traffic but it was really slow.

Leave a Reply

Your email address will not be published. Required fields are marked *