The setup I need to build with HAProxy is the high availability solution consisting of http (http to https redirection must be configured in HAProxy) and https frontends with two backends. Backend servers have HTTPS enabled so HAProxy must establish HTTPS connection to backend servers. We have four backend servers and we want first two servers serve one specific app (/app
path) and the other two – all other requests so we need path-based routing in HAProxy. In addition, there are multiple domains that must be served by backend servers (and HAProxy).
As we need path-based routing, HAProxy must operate in http mode (on the 7th level) allowing it to examine contents of HTTP headers and extract Host
and Path
headers. Later, the incoming request must be sent to a proper backend based on the Path
header and connection to backend server must be established via HTTPS. As multiple domains must be served by HAProxy and backends we need to install multiple certificates in HAProxy and enable Server Name Indication (SNI) with backend servers.
Here is how configure HAProxy in that way step by step.
So, first things first. Let’s create HTTP frontend and redirect all HTTP traffic to HTTPS.
Redirect all HTTP traffic to HTTPS in HAProxy
We need to redirect all incoming HTTP traffic (port 80) to HTTPS (port 443). To do that we define a frontend and use an ACL to detect the HTTP protocol and redirect using the http-request directive.
1 2 3 4 5 |
frontend http bind *:80 mode http acl http ssl_fc,not http-request redirect scheme https if http |
Let’s created HTTPS enabled frontend that must serve multiple domains with their corresponding certificates.
HTTPS frontend with multiple domains and certificates in HAProxy
There are three options here:
- Use two
crt
options when binding. - Concatenate multiple certs in just one PEM file.
- Use
crt
with a path to a folder containing all the certs.
I chose the last option. For that, I created “certs
” folder in /etc/haproxy
and put all pem certificates with corresponding private keys here. Initially, I’d had the certificates in pfx format so I had to convert pfx to pem
As a result, in /etc/haproxy/certs
folder I had 2 certificates (one of them was wildcard) and two private keys. Please note that you must specify private key filename
as certificate's name + .key
extension.
1 2 3 |
# ls /etc/haproxy/certs cert1.pem cert1.pem.key cert2.pem cert2.pem.key |
And in HAProxy config we have:
1 2 |
frontend https bind :443 ssl crt /etc/haproxy/certs |
Next, let’s define simple path-based routing.
Path-based routing in HAProxy
To be able to do path based routing HAproxy must be able to examine HTTP headers, that’s why we configured TLS termination in a previous step. I want all paths beginning with /app/
to be served by “app
” backend and all other requests should be served by “other
” backend. Here is a simple rule we should add:
1 2 3 4 5 |
mode http # route to a backend based on path's prefix use_backend app if { path_beg /app/ } default_backend other |
Let’s create our backends and enable SNI for backends.
Create HTTPS backends with SNI in HAProxy
HAProxy must connect with HTTPS to backend servers that serve multiple domains. So we must pass domain name (Host header) to backend servers so they process our request properly. Without SNI in backend we would get 503 error from backend servers. Here is a configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
backend app mode http cookie SERVERUSED insert indirect nocache balance roundrobin server web1 10.0.1.10:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web1 server web2 10.0.1.11:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web2 backend other mode http cookie SERVERUSED insert indirect nocache balance roundrobin server web3 10.0.1.12:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web3 server web4 10.0.1.13:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web4 |
We have specified two backends: “app
” for paths that begin with /app/
and “other
” for all other requests. Please notice that frontends and backends should be in http mode. Next, I wanted to implement sticky session in HAProxy using cookie SERVERUSED
.
If the user was first relayed to the web1 server, then their cookie will contain the value web1 and HAProxy will know to keep sending them there. The insert
parameter creates the cookie, indirect
removes the cookie on each incoming request before forwarding the message to the server, and nocache
sets the Cache-Control: private HTTP header so that cache servers between HAProxy and the user won’t cache the response.
Next, I set type of load balancing to “roundrobin
” so requests will be distributed equally between backend servers and, finally I define backend servers with “server
” directive. Here I specify server’s name for HAProxy, IP address and port for HAProxy to connect to, tell HAProxy that backend servers use SSL (“ssl
” directive) but HAPRoxy should not verify backend server’s certificate (“verify none
“). “maxconn 30
” allows to limit number of connections and, finally I define SNI options: HAProxy extracts target domain name from “Host” header and sends it to backend server so backend server how which virtual host should process the request. Last but not least I defined SERVERUSED
cookie’s value to server’s name for HAProxy sticky session to work.
Now it’s time to put it all together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
... default configuration ... frontend http bind *:80 mode http acl http ssl_fc,not http-request redirect scheme https if http frontend https bind :443 ssl crt /etc/haproxy/certs mode http # route to a backend based on path's prefix use_backend app if { path_beg /app/ } default_backend other backend app mode http cookie SERVERUSED insert indirect nocache balance roundrobin server web1 10.0.1.10:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web1 server web2 10.0.1.11:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web2 backend other mode http cookie SERVERUSED insert indirect nocache balance roundrobin server web3 10.0.1.12:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web3 server web4 10.0.1.13:443 ssl verify none maxconn 30 check-sni req.hdr(Host) sni req.hdr(Host) cookie web4 |
And that’s it! You now have level 7 HAProxy load balancer with TLS termination, path-based routing, SNI and sticky sessions!
Good luck!