In Part 1 of this guide I outlined the relatively simple process of compiling HAProxy and installing it on Ubuntu 18.

Understanding HAProxy and Basic Configuration

When you install HAProxy from source you will need to supply your own configuration file. Thankfully several examples are provided to get you started, and I recommend starting with the simplest one called ‘transparent proxy’. Obviously you’ll need to change the bind and server addresses in the file to match your requirements.

It’s important to note that HAProxy routes requests to hosts using what are effectively conditional statements. You first define an Access Control List (ACL) and then set a parameter to match this ACL, such as an HTTP Host Header or incoming port. You can then specify one or more backends based on the ACLs you have defined, and traffic will be forwarded to that backend as appropriate. Backends themselves can have multiple servers defined, and different load balancing algorithms can be applied per backend definition.

My Configuration for HTTP/HTTPS over NAT

First let me explain my original use case and then I’ll show you a configuration close to what I ended up with. What I needed was a way to route requests on ports 80 and 443 to different servers on my network, and as I mentioned earlier HAProxy accommodates this with Access Control Lists (ACLs) and conditional statements. This is a look at what was my first working configuration:

    global
        daemon
        maxconn 256
        tune.ssl.default-dh-param 2048

    defaults
        timeout connect 300ms
        timeout client 1000ms
        timeout server 1000ms
        mode http

    frontend traffic_in
        log global
        bind *:80
        bind *:443 ssl crt /path/to/certs/ no-sslv3

        acl server1_in hdr(host) -i server1.kevincottrell.net
        acl server2_in hdr(host) -i server2.kevincottrell.net

        use_backend server1_backend if server1_in
        use_backend server2_backend if server2_in

        backend server1_backend
        server server1 192.168.1.101:80 check

        backend server2_backend
        server server2 192.168.1.103:80 check
        redirect scheme https code 301 if !{ ssl_fc }

There’s a lot going on in this file so let me explain the basic ideas in this configuration:

  • For some reason HAProxy still defaults to 1024 bit Diffie-Hellman… so we raise this to 2048 with tune.ssl.default-dh-param 2048

  • This server will bind all interfaces and listen on both ports 80 and 443 (HTTP and HTTPS) with sslv3 disabled. You may want to specify the interface in yours.

  • It will read the host header (hdr(host)) of incoming packets and set an ACL flag of server1_in or server2_in depending on what it finds.

  • The use_backend statements will route packets to the appropriate backend server based on the ACL.

  • Traffic going to server2 will be redirected to https if the connection is not already secured. This is dictated by the redirect scheme statement.

If you need to do something more elaborate there is extensive documentation available but I encourage you to keep it simple and only change one or two things at a time until you are feeling comfortable with the syntax.

SSL Termination

As you might have noticed in my example above, HAProxy can provide encryption to a service that is using port 80 on your local network. This is known as SSL Termination and is a great way to manage a “secure entry” to your backend servers. Just point HAProxy at the directory containing all of your required certificates and keys on the line that binds port 443 as I did in the example above and it will choose the appropriate certificate and key based on the connection being made. Backends can be made to use SSL as well (meaning HAProxy will use encrypted communication with the backend) but it will result in slightly more overhead and likely won’t scale very well.

Checking your configuration file

HAProxy will not start if your configuration is invalid, but thankfully it does provide a means to check this. It’s as simple as haproxy -c -f filename.conf - where filename.conf is the file where you have stored your configuration. If there are no problems you’ll get a message Configuration file is valid or an error message otherwise.

Run HAProxy with your configuration

After all that there’s not much more to do, just type:

sudo haproxy -f filename.conf

And haproxy will be off and running using the parameters you gave it in your file. You’ll need to manually start and stop the process unless you create a systemd service or some other means to start the process.

Add a systemd unit for HAProxy

Thankfully again the community comes through and provides a systemd unit file in the contrib directory of the source you downloaded earlier. You just need to modify it slightly by removing or commenting out the two EnvironmentFile lines and replacing them with an Environment value that includes a few key pieces of information for HAProxy, like this:

#EnvironmentFile=-/etc/default/haproxy
#EnvironmentFile=-/etc/sysconfig/haproxy
Environment="CONFIG=/path/to/your/config.conf" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock"

You could alternatively create an environment file with the same values we have on the Environment line, but that’s a matter of preference. Once those changes are made you can copy this file to /etc/systemd/system/haproxy-custom.service and type in the following commands:

sudo systemctl daemon-reload
sudo systemctl enable haproxy-custom.service
sudo systemctl start haproxy-custom.service

The daemon-reload command tells the system that you want it to reload the system service files, the enable command sets the system to start your service on boot, and the start command triggers this service on boot.

What was it all for?

So with that I hope you’ll be able to understand how useful HAProxy has been for me given the story I gave you in Part 1. This allows me to have a single point of ingress to my home network which can handle all of the SSL termination to the outside world and then route ports 80 and 443 to any internal IP I choose based on various ACLs I define. As far as port forwarding is concerned I just need to send all traffic on ports 80 and 443 to the HAProxy server and then I can manipulate everything from there with the HAProxy configuration file. It’s fantastic!

One thing that was critical to the success of this project was the use of LetsEncrypt and certbot. I highly recommend them and I will make another guide in the future explaining how HAProxy handles all of my certificate renewals using them.

I hope this was helpful or at least interesting to you, and if so check back soon as I’ll be documenting my other projects on a (hopefully) more regular basis.