HAProxy can make your application unreachable if you configure it to listen on an IP address that doesn’t exist on any of your network interfaces.

Here’s how to set up HAProxy for high availability, breaking down its core components: frontend, backend, and listen directives.

Let’s start with a basic HAProxy configuration file (haproxy.cfg):

global
    log /dev/log local0
    log /dev/log local1 notice
    maxconn 4000
    user haproxy
    group haproxy
    daemon

defaults
    log global
    mode http
    option httplog
    option dontlognull
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http_frontend
    bind *:80
    default_backend http_backend

backend http_backend
    balance roundrobin
    server app1 192.168.1.10:80 check
    server app2 192.168.1.11:80 check

Frontend: The Public Face

The frontend section defines how HAProxy accepts incoming connections. Think of it as the reception desk of your application. It specifies the IP addresses and ports HAProxy will listen on.

In our example:

frontend http_frontend
    bind *:80
    default_backend http_backend
  • bind *:80: This tells HAProxy to listen on all available network interfaces (*) on port 80. This is the standard port for HTTP traffic. You could also specify a specific IP address, like bind 192.168.0.100:80, if you only want HAProxy to listen on that particular interface.
  • default_backend http_backend: This is a crucial directive. It tells HAProxy that any traffic arriving on this frontend should be directed to the http_backend for processing. You can have multiple default_backend directives or more complex rules using acl (Access Control List) and use_backend to route traffic to different backends based on request criteria.

Backend: The Workers

The backend section defines the group of servers that will actually handle the requests. These are your application servers. HAProxy will distribute the incoming traffic among these servers.

In our example:

backend http_backend
    balance roundrobin
    server app1 192.168.1.10:80 check
    server app2 192.168.1.11:80 check
  • balance roundrobin: This specifies the load balancing algorithm. roundrobin is the simplest: HAProxy sends each incoming request to the next server in the list, cycling through them. Other common algorithms include leastconn (sends requests to the server with the fewest active connections) and source (hashes the source IP address to consistently send a client to the same server).
  • server app1 192.168.1.10:80 check: This defines a specific backend server.
    • app1: A descriptive name for the server.
    • 192.168.1.10:80: The IP address and port of the actual application server.
    • check: This enables health checking for the server. HAProxy will periodically send a small request (e.g., an HTTP GET to /) to app1 on port 80. If the server doesn’t respond within the configured timeout or returns an error, HAProxy will mark it as "down" and stop sending traffic to it until it becomes healthy again.

Listen: The Combined Approach

The listen directive is a shorthand that combines both frontend and backend configurations into a single block. It’s useful for simpler setups where you don’t need complex routing logic.

Here’s an equivalent configuration using listen:

listen http_listener
    bind *:80
    mode http
    balance roundrobin
    server app1 192.168.1.10:80 check
    server app2 192.168.1.11:80 check
  • listen http_listener: Defines a listening service named http_listener.
  • The directives within this block (bind, mode, balance, server) are the same as you would find in separate frontend and backend sections. HAProxy treats this as a frontend that immediately directs traffic to the listed servers using the specified balancing method.

This listen block essentially says: "Listen on all interfaces, port 80, for HTTP traffic, and distribute it using round-robin to app1 and app2, checking their health."

The most surprising thing about HAProxy’s traffic distribution is that it can be incredibly granular, even down to individual HTTP headers or cookies, allowing for sophisticated routing that goes far beyond simple IP-based balancing.

Consider this scenario where you want to send requests for API version 1 to one set of servers and API version 2 to another:

frontend api_frontend
    bind *:8080
    acl api_v1 hdr(Host) -i api.example.com
    acl api_v2 hdr(Host) -i api-v2.example.com

    use_backend api_v1_backend if api_v1
    use_backend api_v2_backend if api_v2
    default_backend api_v1_backend # Fallback for requests not matching v2

backend api_v1_backend
    server api1_v1 192.168.2.10:8000 check
    server api2_v1 192.168.2.11:8000 check

backend api_v2_backend
    server api1_v2 192.168.2.20:8000 check
    server api2_v2 192.168.2.21:8000 check

Here, acl api_v1 hdr(Host) -i api.example.com creates a condition that is true if the incoming request’s Host header matches api.example.com (case-insensitive). use_backend api_v1_backend if api_v1 then directs traffic that satisfies the api_v1 ACL to the api_v1_backend. This level of control allows you to manage diverse application versions or services behind a single entry point.

One aspect of HAProxy’s health checking that often trips people up is the inter (interval) and fall/rise parameters. By default, HAProxy checks every 2 seconds (inter 2000ms), marks a server down after 3 failures (fall 3), and marks it back up after 2 successes (rise 2). If you have a server that’s slow to respond but fundamentally healthy, you might see it flapping between up and down states. Adjusting these values, for example server app1 192.168.1.10:80 check inter 5000ms fall 5 rise 3, can provide more stability by giving servers more time to recover before being marked as unhealthy, and requiring more consecutive successes to be considered healthy again.

The next step in mastering HAProxy is understanding how to implement SSL/TLS termination and manage certificate rotation.

Want structured learning?

Take the full Haproxy course →