Synopsis
Some time ago, we wrote an article which explained how to load-balance SSL services, maintaining affinity using the SSLID.
The main limitation of this kind of architecture is that you must dedicate a public IP address and port per service.
If you’re hosting web or mail services, you could run out of public IP address quickly.
TLS protocol has been extended in 2003, RFC 3546, by an extension called SNI: Server Name Indication, which allows a client to announce in clear the server name it is contacting.
NOTE: two RFC have obsoleted the one above, the latest one is RFC 6066
The ALOHA Load-balancer can use this information to choose a backend or a server.
This allows people to share a single VIP for several services.
Of course, we can use SNI switching with SSLID affinity to build a smart and reliable SSL load-balanced platform.
NOTE: Server Name information is sent with each SSL Handshake, whether you’re establishing a new session or you’re resuming an old one.
SNI is independent from the protocol used at layer 7. So basically, it will work with IMAP, HTTP, SMTP, POP, etc…
Limitation
Bear in mind, that in 2012, not all clients are compatible with SNI.
Concerning web browsers, a few of used in 2012 them are still not compatible with this TLS protocol extension.
We strongly recommend you to read the Wikipedia Server Name Indication page which lists all the limitation of this extension.
- Only HAProxy nightly snapshots from 8th of April are compatible (with no bug knows) with it.
- Concerning ALOHA, it will be available by ALOHA Load-balancer firmware 5.0.2.
Diagram
The picture below shows a platform with a single VIP which host services for 2 applications:
We can use SNI information to choose a backend, then, inside a backend, we can use SSLID affinity.
Configuration
Choose a backend using SNI TLS extension
The configuration below matches names provided by the SNI extention and choose a farm based on it.
In the farm, it provides SSLID affinity.
If no SNI extention is sent, then we redirect the user to a server farm which can be used to tell the user to upgrade its software.
# Adjust the timeout to your needs defaults timeout client 30s timeout server 30s timeout connect 5s # Single VIP with sni content switching frontend ft_ssl_vip bind 10.0.0.10:443 mode tcp tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } acl application_1 req_ssl_sni -i application1.domain.com acl application_2 req_ssl_sni -i application2.domain.com use_backend bk_ssl_application_1 if application_1 use_backend bk_ssl_application_2 if application_2 default_backend bk_ssl_default # Application 1 farm description backend bk_ssl_application_1 mode tcp balance roundrobin # maximum SSL session ID length is 32 bytes. stick-table type binary len 32 size 30k expire 30m acl clienthello req_ssl_hello_type 1 acl serverhello rep_ssl_hello_type 2 # use tcp content accepts to detects ssl client and server hello. tcp-request inspect-delay 5s tcp-request content accept if clienthello # no timeout on response inspect delay by default. tcp-response content accept if serverhello stick on payload_lv(43,1) if clienthello # Learn on response if server hello. stick store-response payload_lv(43,1) if serverhello option ssl-hello-chk server server1 192.168.1.1:443 check server server2 192.168.1.2:443 check # Application 2 farm description backend bk_ssl_application_2 mode tcp balance roundrobin # maximum SSL session ID length is 32 bytes. stick-table type binary len 32 size 30k expire 30m acl clienthello req_ssl_hello_type 1 acl serverhello rep_ssl_hello_type 2 # use tcp content accepts to detects ssl client and server hello. tcp-request inspect-delay 5s tcp-request content accept if clienthello # no timeout on response inspect delay by default. tcp-response content accept if serverhello stick on payload_lv(43,1) if clienthello # Learn on response if server hello. stick store-response payload_lv(43,1) if serverhello option ssl-hello-chk server server1 192.168.2.1:443 check server server2 192.168.2.2:443 check # Sorry backend which should invite the user to update its client backend bk_ssl_default mode tcp balance roundrobin # maximum SSL session ID length is 32 bytes. stick-table type binary len 32 size 30k expire 30m acl clienthello req_ssl_hello_type 1 acl serverhello rep_ssl_hello_type 2 # use tcp content accepts to detects ssl client and server hello. tcp-request inspect-delay 5s tcp-request content accept if clienthello # no timeout on response inspect delay by default. tcp-response content accept if serverhello stick on payload_lv(43,1) if clienthello # Learn on response if server hello. stick store-response payload_lv(43,1) if serverhello option ssl-hello-chk server server1 10.0.0.11:443 check server server2 10.0.0.12:443 check
Choose a server using SNI: aka SSL routing
The configuration below matches names provided by the SNI extention and choose a server based on it.
If no SNI is provided or we can’t find the expected name, then the traffic is forwarded to server3 which can be used to tell the user to upgrade its software.
# Adjust the timeout to your needs defaults timeout client 30s timeout server 30s timeout connect 5s # Single VIP frontend ft_ssl_vip bind 10.0.0.10:443 mode tcp tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } default_backend bk_ssl_default # Using SNI to take routing decision backend bk_ssl_default mode tcp acl application_1 req_ssl_sni -i application1.domain.com acl application_2 req_ssl_sni -i application2.domain.com use-server server1 if application_1 use-server server2 if application_2 use-server server3 if !application_1 !application_2 option ssl-hello-chk server server1 10.0.0.11:443 check server server2 10.0.0.12:443 check server server3 10.0.0.13:443 check
Related Links
- HOWTO SSL native in HAProxy
- Maintain affinity based on SSL session ID
- Send users to the same backend for both HTTP and HTTPS
- Scaling out SSL
- Benchmarking SSL performance
- Transport Layer Security (TLS) Extensions (RFC 3546)
- Transport Layer Security (TLS) Extensions: Extension Definitions (RFC 6066)
- Wikipedia Server Name Indication
Hi,
Thanks for this, very usefull 🙂
Is it possible to specifiy multiple ssl certs ? For example one for application 1 and another for application 2 ?
In pound in can set à list of certificates to use, how can i do the same ?
Thanks
Hi,
Of course you can precise a list of certificates to use and HAProxy will pick the right one based on SNI. Just specify several crt directive on the bind line.
You can also make crt points to a directory, then HAProxy will load all the certs from this directory.
Baptiste
Hello Baptiste,
When using a directory, will HAProxy select the correct crt for the correct domain?
We are trying to send many domains at our head IP and some will have SSL and some won’t.
Our tests have shown that if we try to go to a domain that has NO cert, HAProxy tries to give one up anyways resulting in a browser warning saying this certificate is bad instead of the SSL Connection Error.
Any thoughts?
Hi,
HAProxy select the right certificate based on SNI sent by the client.
Unfortunately, not all clients send it (Cf win XP and some mobile application).
Then HAProxy delivers the first certificate it found in the directory.
Baptiste
Hey,
thanks a lot for this example. It helps me to solve my problem with different https servers using TCP Mode.
Fred
Thank you for the example!! Saved me hours.
This is very helpfull. But how can i use host base routing for same host for both 443 and 80 like app1.domain.com
frontend http
bind *:80
mode tcp
tcp-request inspect-delay 5s
acl mytonicnonssl req_ssl_sni -i staging.mytonic.com
use_backend mytonic-ssl if mytonicnonssl
backend mytonic-nonssl
mode tcp
balance roundrobin
server server1 10.10.17.222:8080 check
above configuration is not working for me. but ssl is working fine as described in this blog.
you need a “tcp-request content” rule to wait for the SNI to be presented (in the client hello). Try with this after your tcp-request rule :
tcp-request content accept if { req_ssl_hello_type 1 }
Also in general you need to keep in mind that it’s not a good idea to use the same port for both SSL and non-SSL. If you want to do the normal stuff consisting in having both 80 and 443 on the same frontend, it’s as simple as :
bind :80
bind :443 ssl crt foo.pem
Traffic on port 80 will be processed as-is, traffic on port 443 will be decrypted before being processed. In both cases you have the Host header field which is the only valid authority to decide what backend to use.