DEV: Load Balancing with HAProxy
HAProxy is a free, open source, high-performance and reliable reverse proxy offering high availability, load balancing, and proxying for TCP and HTTP-based applications.
Known for its speed, reliability, and rich feature set, HAProxy is widely used to distribute traffic across multiple servers, ensuring high availability and improved scalability.
In this tutorial, you will learn how to:
- Install HAProxy on Ubuntu
- Configure HAProxy to balance traffic between multiple backend services
Why bother?
You might wonder why it's worth learning to use a load balancer like HAProxy when modern container orchestration platforms and cloud providers often handle it for you—arguably better than most of us could configure manually.
But despite the rise of cloud-native technologies, traditional infrastructure hasn't disappeared—and likely never will. Many production environments still rely on tried-and-true components like HAProxy for performance, control, and simplicity.
Even in Kubernetes or cloud-managed setups, load balancing is often powered by tools like HAProxy behind the scenes. Learning how these components work gives you a deeper understanding of what's happening under the hood, making you better equipped to troubleshoot, optimize, and build more resilient systems.
💡 To dive deeper into the concepts covered in this tutorial, check out the References section.
How it works
HAProxy sits in front of a group of backend services and acts as a gateway. Instead of users connecting directly to those services, they connect to HAProxy, which then forwards each request to one of the available backends.
Load balancing with HAProxy
Which backend HAProxy chooses depends on various factors, such as backend health, current load, and the configured load balancing algorithm.
Explaining these algorithms in detail is outside the scope of this tutorial, but as a quick example, the default method is round-robin, where each request is sent to the next backend in sequence, cycling through all of them evenly.
If one of the backends becomes overloaded or worse, goes down entirely, HAProxy will automatically stop sending requests to it without users noticing a thing.
Maintain availability with a backend down
Preparation
In this tutorial, you'll work with several machines, each serving a specific role:
- dev-machine – Your "local machine" used to do testing (i.e. send requests to HAProxy).
- loadbalancer – The machine where HAProxy will be installed and configured.
- backends – Web servers acting as backend services, each listening on port 80.
Network topology
Your task is to install and configure HAProxy, then observe how it behaves under normal and certain failure conditions.
Before you begin, take a moment to get familiar with the environment you'll be working in.
A couple things to check:
dev-machine can reach the backends (although this isn't necessary):
curl http://backend-0[1-3]
Hello from backend-01!
Hello from backend-02!
Hello from backend-03!
dev-machine can reach the loadbalancer (nothing should be listening yet on port 80):
sudo ping -c 2 loadbalancer
curl http://loadbalancer
PING loadbalancer (172.16.0.254) 56(84) bytes of data.
64 bytes from loadbalancer (172.16.0.254): icmp_seq=1 ttl=64 time=0.013 ms
64 bytes from loadbalancer (172.16.0.254): icmp_seq=2 ttl=64 time=0.059 ms
curl: (7) Failed to connect to loadbalancer port 80 after 0 ms: Couldn't connect to server
Installing HAProxy
Package source
HAProxy is available in most Linux distributions, but default package versions are often outdated.
Fortunately, the HAProxy team maintains an official package repository that provides up-to-date and supported versions for easy installation.
While this tutorial should work with any recent HAProxy version, you may want to install one of the latest stable releases to take advantage of the newest features and improvements:
sudo apt-get install -y --no-install-recommends software-properties-common
sudo add-apt-repository -y ppa:vbernat/haproxy-3.2
Proceed with the installation steps below.
Go to the loadbalancer and install the haproxy package:
sudo apt-get install -y haproxy
By default, HAProxy loads its configuration from a single file at /etc/haproxy/haproxy.cfg.
While this works well for simple setups, it's often a good idea to separate configurations for better organization and maintainability.
Fortunately, HAProxy makes this easy with a small configuration change:
echo 'CONFIG="/etc/haproxy"' | sudo tee -a /etc/default/haproxy
This configures the HAProxy service to load all .cfg files from the /etc/haproxy directory.
You can now start the service:
sudo systemctl enable --now haproxy
HAProxy concepts
Before configuring HAProxy, it's important to understand a few core concepts.
Frontends and backends
In HAProxy, frontends and backends define how traffic moves through the proxy.
A frontend is where client connections arrive. It specifies how HAProxy receives incoming requests (such as the IP address, port, and protocol) and how to route them.
Once HAProxy selects a destination, it forwards the request to a backend, which represents a group of servers that handle the actual work. Backends define load-balancing behavior, health checks, and connection settings to control how requests are distributed.
Protocols
HAProxy supports two operating modes: TCP mode and HTTP mode, corresponding to Layer 4 (transport) and Layer 7 (application) of the OSI model.
In TCP mode, HAProxy treats traffic as a raw byte stream. It doesn't parse or interpret the data, making this mode ideal for encrypted or non-HTTP protocols like SSL/TLS, MySQL, or custom binary formats.
In HTTP mode, HAProxy understands and parses HTTP traffic. This enables features like URL-based routing, header manipulation, cookie management, and detailed access logs.
Choosing the right mode depends on your use case: use TCP mode for encrypted or non-HTTP traffic, and HTTP mode when you need full visibility and control over web traffic.
Configuring HAProxy
Now that you're familiar with HAProxy's core concepts, let's walk through some example configurations.
Tips
Configuration files should be placed in /etc/haproxy and use a .cfg extension.
You can edit these files with any text editor you prefer:
sudo $EDITOR /etc/haproxy/CONFIG.cfg
Alternatively, you can use here documents:
sudo tee /etc/haproxy/CONFIG.cfg <<EOF
FILE HERE
EOF
Restart the HAProxy service after each change for the new configuration to take effect:
sudo systemctl restart haproxy
HTTP (Layer 7) load balancing
In this section, you will configure HAProxy to accept HTTP traffic at its frontend and forward it to one of the backends.
Let's configure the frontend first by creating a frontend.cfg in the HAProxy configuration directory:
frontend my_frontend
mode http
bind :80
Restart the service:
sudo systemctl restart haproxy
You should now be able to send requests to the load balancer, though it won't return much in response just yet.
curl http://loadbalancer
<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>
Next, set up the backend configuration:
backend my_backend
mode http
balance roundrobin
server backend-01 backend-01:80 check
server backend-02 backend-02:80 check
server backend-03 backend-03:80 check
Configuration breakdown
balancetells HAProxy which load-balancing algorithm to usecheckat the end ofservertells HAProxy to perform health checks
Finally, configure the frontend to route incoming requests to the appropriate backend:
frontend my_frontend
mode http
bind :80
default_backend my_backend
Restart the service:
sudo systemctl restart haproxy
TCP (Layer 4) load balancing
If you look at the configuration above, you'll notice it doesn't take advantage of the HTTP context: there's no TLS termination, no routing based on paths or headers, and no access logging. Even though the backends serve HTTP, HAProxy isn't using that information for load balancing.
In this case, you might as well use TCP mode instead. It's slightly more efficient, since HAProxy doesn't need to parse or inspect the contents of each request.
Update the mode setting in both the frontend and the backend to tcp:
sudo sed -i 's/mode http/mode tcp/g' /etc/haproxy/{frontend,backend}.cfg
Restart the service:
sudo systemctl restart haproxy
Bonus: frontend+backend=listen
TODO
listen my_lb
mode tcp
bind :81
balance roundrobin
server backend-01 backend-01:80 check
server backend-02 backend-02:80 check
server backend-03 backend-03:80 check
Restart the service:
sudo systemctl restart haproxy
Summary
🎉 Congratulations!
You've learned the core concepts needed to run HAProxy and should now be able to set up load balancing between services.
TODO
References
TODO
Learn by doing, not just by reading or watching
Sign up for a free account to start a VM playground right on this page, track your progress, and get notified about new learning materials.
Writes about
Frequently covers

Getting Started with VictoriaMetrics on Kubernetes
Deploy VictoriaMetrics on Kubernetes using the VM Operator, configure metrics scraping with CRDs, and query cluster metrics.
Getting started with Valkey on Architect
Learn how to run Valkey on spot instances without losing state
Kubernetes: Runtime Class
Learn how to configure and use Kubernetes Runtime Classes to specify different container runtimes for your workloads. Explore different OCI runtimes and their scheduling constraints.
Kubernetes: Admission Control
Dive deep into Kubernetes admission control with hands-on examples. Learn how admission controllers influence cluster behavior, apply default configurations, and enforce policies for consistency and compliance.