Posted on April 12, 2011

My new setup involves routing from a load balancer / proxy NGINX instance to NGINX backend webservers running on different IPs. This works great and is relatively simple until you start dealing with HTTPS and SSL. The tricky part is that, since the webservers and load balancer are talking on a local network, there's no need to encrypt the traffic. The load balancer handles HTTPS SSL connections and forwards the traffic over HTTP to the webservers. We do, however, want the backend servers to respond AS IF they were using HTTPS. That is, we want links to be build using the HTTPS scheme. So basically we want to force the backend webservers to tell the application servers that we're using HTTPS when we really aren't.

I accomplished this with a combination of setting 'X-Forwarded-Proto https' on the proxy/loadbalancer and sniffing out for that header on the backend. When the header is set, the backend NGINX sets a variable $https, which is read in fastcgi_params and passed along to the web servers running in CGI.

Frontend proxy config:

server {
    listen 443;

    ssl  on;
    ssl_certificate  /etc/certs/example/example.crt;
    ssl_certificate_key  /etc/certs/example/example.key;
    ssl_session_timeout  5m;
    ssl_protocols  SSLv2 SSLv3 TLSv1;
    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    ssl_prefer_server_ciphers   on;
    # tell backend to behave as https even though we are not ssl on private net
    proxy_set_header X-Forwarded-Proto https; 

    location / {
       proxy_pass; # set explicitly in /etc/hosts

In the backend webserver nginx HTTP stanza:

# handle non-ssl https from the nginx load balancer 
# (we are not ssl'd on the internal network but we need https sent to fastcgi)                     
map $http_x_forwarded_proto $https {
    default off;
    https on;

And finally in the backend webserver's fastcgi_params file:

# if the load balancer sends back X-Forwarded-Proto, we set $https so 
# that web apps know to serve https shiz
fastcgi_param HTTPS $https;

This was totally not obvious to me and took me a couple of hours to figure out. I was kinda hoping NGINX would just "do the right thing" when it noticed the Proto header, but it didn't. So I had to force things to work the way I wanted them to.

Hope this helps someone!

comments powered by Disqus