Using HAProxy to offload SSL


At Qandidate.com we started to use Docker containers to run our apps and REST APIs. Some of them are publicly exposed and should communicate over a secure connection. Of course we can config nginx in our containers to accept secure connections, but I want to show you how easy it is to use HAProxy to do the SSL offloading.

Let's get started. In this post I used a sandbox Vagrant environment with Ubuntu 14.04. I set it up with the following Vagrantfile:

Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

end

HAProxy has built-in SSL support starting with version 1.5. Ubuntu 14.04LTS ships with 1.4, so we have to add a repository with HAProxy 1.5:

$ sudo add-apt-repository ppa:vbernat/haproxy-1.5
$ sudo apt-get update

Install HAProxy 1.5 with the following command:

$ sudo apt-get install haproxy

Configure HAProxy to allow HTTPS connections by appending the following to your /etc/haproxy/haproxy.cfg file.

frontend http-in
    mode    http
    bind *:443 ssl crt .

Place the SSL certificate for your domain in /etc/ssl/private or create a new self-signed one using the following command:

$ openssl req -x509 -new -nodes -newkey rsa:2048 -keyout site1.key -out site1.crt -days 365

Answer the questions accordingly. The only important question right now is "Common Name". Answer that question with the domain name (e.g. qandidate.com) you're generating the certificate for. In this example I created a certificate with common name site1.

Now create a .pem file by concatenating the .crt and the .key files.

$ cat site1.crt site1.key | sudo tee /etc/ssl/private/site1.pem

Tell HAProxy which back-end it has to use to route the traffic to. Use ssl_fc_sni to test with certificate is used. The argument of ssl_fc_sni should match the "Common Name" of the certificate. In this example we used site1.

Now update your /etc/haproxy/haproxy.cfg :

frontend http-in
    bind *:443 ssl crt .
    use_backend backend_site1 if { ssl_fc_sni site1 }

Add the backend to the configuration file:

backend backend_site1
    balance roundrobin
    option httpclose
    option forwardfor
    server s1 127.0.0.1:8081 maxconn 32

Restart HAProxy.

$ sudo service haproxy restart

Multiple sites

To proxy to multiple sites, just create a certificate for the second site and add it to /etc/ssl/private/ as well.

Create a second certificate for site2. Enter site2 when asked for a Common name.

$ openssl req -x509 -new -nodes -newkey rsa:2048 -keyout site2.key -out site2.crt -days 365
$ cat site2.crt site2.key | sudo tee /etc/ssl/private/site2.pem

Tell HAProxy which backend to use.

frontend http-in
    bind *:443 ssl crt .
    use_backend backend_site1 if { ssl_fc_sni site1 }
    use_backend backend_site2 if { ssl_fc_sni site2 }

Add the extra backend.

backend backend_site2
    balance roundrobin
    option httpclose
    option forwardfor
    server s2 127.0.0.1:8082 maxconn 32

Restart HAProxy.

Note: Repeat the steps to add more sites.

See it in action

To see HAProxy in action create two simple php websites.

$ sudo apt-get install php5-cli
$ mkdir site1
$ mkdir site2
$ echo -e "<?php\n\necho 'site1\\n';" > site1/index.php
$ echo -e "<?php\n\necho 'site2\\n';" > site2/index.php

Start the web servers with the following commands

$ php -S 127.0.0.1:8081 -t site1 &
$ php -S 127.0.0.1:8082 -t site2 &

Finally add the following lines to your /etc/hosts

127.0.0.1 site1
127.0.0.1 site2

See it in action:

$ curl -o - -k https://site1
$ curl -o - -k https://site2

-k is used to allow self signed certificates.

See how easy it is to use HAProxy to do the SSL offloading. The hard part was generating the certificates ;). In a following post I want to show how to only accept connections from trusted clients by being your own certificate authority and signing your own certificates.