Speeding up HAProxy SSL with multiple CPU processes.

This is a guide on how to speed up SSL on HAProxy. In this post, I will show you how to dedicate specific CPU processes to HTTP and HTTPS traffic. i.e. We will dedicate one HAProxy process to plain HTTP traffic and three processes to HTTPS / SSL traffic.

Why?

The answer is pretty simple. Plain HTTP routing is less expensive than SSL. HTTPS requires SSL handshakes and a lot of other CPU intensive operations. By doing this, we give HTTP it’s own space and we give SSL more cores to work off.

Figure out how many CPU cores you have.

Firstly, you will need to figure out how many CPU cores that your system has. A good rule of thumb is to create one HAProxy process for each core that is on your system. By default, HAProxy only uses one core, so we will need to change that.

If you do not know how many CPU cores your system has, then you can run the following Linux command:

echo Cores = $(( $(lscpu | awk '/^Socket/{ print $2 }') * $(lscpu | awk '/^Core/{ print $4 }') ))

In my case, the above command returned “Cores = 4”, so I will create four HAProxy processes.

Setting up the HAProxy multi-process model with nbproc.

TheĀ nbproc parameter allows us to tell HAProxy how many processes it should use. In the configuration example below, I created four separate processes and then mapped them to a specific CPU set:

#This is placed in the global section of the HAProxy config file.
nbproc 4
cpu-map 1 0
cpu-map 2 1
cpu-map 3 2
cpu-map 4 3

Note that the nbproc parameter should be placed in the global section of your haproxy.cfg file.

What does the cpu-map directive do?

The cpu-map directive binds a process to a specific CPU set. From what I’ve read on the subject, using nbproc without the cpu-map directive is not as effective. This is because the default behavior of the Linux kernel is for processes to inherit CPU affinity from the parent. Essentially, this means that without the cpu-map directive, each HAProxy process would run on the same CPU. So instead of having four processes running on four cores, we would end up with four processes running on one CPU core.

That is not what we want to do here.

Binding processes to ports.

Now that we have four processes, we can bind them to specific ports. On a regular HAProxy setup, you might have something like this in your listen section:

#Port 80 / plain HTTP
bind 0.0.0.0:80 tfo
bind :::80 v6only tfo 

#Port 443 / HTTPS / SSL
bind *:443 ssl tfo crt /etc/ssl/site.com/site.com.pem
bind :::443 v6only ssl tfo crt /etc/ssl/site.com/site.com.pem

In the configuration above, HAProxy is listening on the IP4 and IP6 versions of port 80 and port 443.

What we want to do is dedicate one process to port 80 and three processes to port 443. To do this, we can use the process option like so:

#Bind Process 1 to port 80.
bind 0.0.0.0:80 tfo process 1
bind :::80 v6only tfo process 1

#Bind Processes 2-4 to port 443.
bind *:443 ssl tfo crt /etc/ssl/site.com/site.com.pem process 2
bind :::443 v6only ssl tfo crt /etc/ssl/site.com/site.com.pem process 2

bind *:443 ssl tfo crt /etc/ssl/site.com/site.com.pem process 3
bind :::443 v6only ssl tfo crt /etc/ssl/site.com/site.com.pem process 3

bind *:443 ssl tfo crt /etc/ssl/site.com/site.com.pem process 4
bind :::443 v6only ssl tfo crt /etc/ssl/site.com/site.com.pem process 4

If you have separate frontend sections for HTTP and HTTPS, then you could so something like this:

frontend plain
    bind 0.0.0.0:80
    bind-process 1

frontend secure
    bind 0.0.0.0:443 ssl crt /etc/site.pem
    bind-process 2 3 4

In the configuration above, we did the exact same thing in a different way.

Confirming.

Once you are happy with your changes and HAProxy has been reloaded, you can confirm that your changes have taken effect by running the following command:

sudo ss -tplen | grep haproxy

When I ran the command above, I saw the following results:

LISTEN 0 60000   *:80      *:*   users:(("haproxy",pid=18146,fd=5)) ino:185170177 sk:a53 <->
LISTEN 0 60000   *:443     *:*   users:(("haproxy",pid=18149,fd=13)) ino:185170183 sk:a54 <->
LISTEN 0 60000   *:443     *:*   users:(("haproxy",pid=18148,fd=9)) ino:185170181 sk:a55 <->
LISTEN 0 60000   *:443     *:*   users:(("haproxy",pid=18147,fd=7)) ino:185170179 sk:a56 <->
LISTEN 0 60000   :::80     :::*  users:(("haproxy",pid=18146,fd=6)) ino:185170178 sk:a57 v6only:1 <->
LISTEN 0 60000   :::443    :::*  users:(("haproxy",pid=18149,fd=14)) ino:185170184 sk:a58 v6only:1 <->
LISTEN 0 60000   :::443    :::*  users:(("haproxy",pid=18148,fd=10)) ino:185170182 sk:a59 v6only:1 <->
LISTEN 0 60000   :::443    :::*  users:(("haproxy",pid=18147,fd=8)) ino:185170180 sk:a5a v6only:1 <->

If you look at the PIDs, you can see that four separate HAProxy processes exist and that three of them are listening on port 443 (if you are wondering why there are eight lines above, it’s because we are supporting IPv4 and IPv6 on each port).