Last time, I looked at configuring Server Name Indication (SNI) with Apache. It just so happened I needed to set up SNI with the HAProxy Load Balancer last week, so let’s take a look at that.

Typically, when you are hosting multiple sites with HAProxy, you do something like:

use_backend app1-servers if { hdr(host) -i }
use_backend app2-servers if { hdr(host) -i }
use_backend app3-servers if { hdr(host) -i }

This selects the backend to use based on the HTTP Host header.

When you add HTTPS to the mix, there are two ways that HAProxy can handle it, either by terminating SSL or by passing it through.

When HAProxy is terminating SSL, it has the SSL cert and is responsible for encrypting and decrypting the traffic. It may also talk to the backend using HTTPS, but on secure internal network this is usually skipped. In ASCII it looks like:

client <--> HTTPS <--> HAProxy <--> HTTP <--> backend server

When HAProxy is passing though HTTPS traffic it simple sends the raw TCP stream through to the backend which has the certificate and handles encryption and decryption.

HAProxy HTTPS setups can be a little tricky. So make sure you have a working one first before adding SNI to the mix.

When using HAProxy to terminate HTTPS connections, you bind a front end to port 443, and give it an SSL certificate:

frontend https
    bind *:443 ssl crt /etc/haproxy/ssl/

The .pem file needs to contain the private key, the certificate, and any intermediate certificate as well. Something like:

cat bundle.crt >

should do the trick.

All you need to do to enable SNI is to be give HAProxy multiple SSL certificates:

frontend https
    bind *:443 ssl crt /etc/haproxy/ssl/ /etc/haproxy/ssl/ /etc/haproxy/ssl/

That will cause the right certificate to be automatically selected. After that the Host header can be used just as it would be for HTTP.

In pass-through mode SSL, HAProxy doesn’t have a certificate because it’s not going to decrypt the traffic and that means it’s never going to see the Host header. Instead it needs to be told to wait for the SSL hello so it can sniff the SNI request and switch on that:

frontend https
  bind *:443 ssl
  mode tcp
  tcp-request inspect-delay 5s
  tcp-request content accept if { req_ssl_hello_type 1 }

  use_backend app1-servers if { req_ssl_sni -i }
  use_backend app2-servers if { req_ssl_sni -i }
  use_backend app3-servers if { req_ssl_sni -i }

Once again, SNI is simple and easy, so why aren’t you using it?