4

I am currently using the following (simplified) configuration to proxy http and https connections over the same port (required by aws elastic beanstalk):

server {
    listen 777 ssl;
    server_name foo.com;
    ssl_certificate /etc/nginx/ssl/foo.crt;
    ssl_certificate_key /etc/nginx/ssl/foo;

    root /usr/share/nginx/www;
    index index.html index.htm;

    error_page 418 = @upstream;
    error_page 497 = @upstream;

    location / {
        return 418;
    }

    location @upstream {
        proxy_set_header  X-Forwarded-Proto  $scheme;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8080;
    }
}

I want to change upstream to uwsgi and let uwsgi handle ssl because it would simplify deployment.

How can I adjust my configuration to work for SNI HTTPS and HTTP without Nginx decrypting the ssl traffic?

1
  • 1
    I've been looking for a way to do this, so that I can have nginx sitting in front of sniproxy - but it still seems that nginx won't read the sni address without trying to decrypt the traffic first. ("no ssl_certificate" errors in the log). :-( – Coops Jul 28 '15 at 20:04
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
1
  • I figured this was the case. I just wanted to use nginx for this particular implementation instead of haproxy. – user319862 Sep 14 '14 at 18:21
5

It looks like this is now supported using the ngx_stream_ssl_preread_module module

2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.