$linuxjunkies
>

Install and Configure HAProxy

Install HAProxy and configure frontends, backends, ACL-based routing, TLS termination, sticky sessions, and HTTP health checks on modern Linux distros.

IntermediateUbuntuDebianFedoraArch10 min readUpdated May 26, 2026

Before you start

  • Root or sudo access on the target server
  • One or more backend application servers reachable by IP
  • A valid TLS certificate and private key if configuring HTTPS
  • Ports 80 and 443 open in your firewall (ufw, firewalld, or nftables)

HAProxy is the de facto standard for high-availability load balancing on Linux. It handles millions of requests per second in production at companies like GitHub and Reddit, and it does so with a configuration file that is genuinely readable once you understand its structure. This guide walks through a full HAProxy setup: installing the daemon, defining frontends and backends, writing ACL rules, terminating TLS, enabling sticky sessions, and wiring up health checks. Commands are shown for Debian/Ubuntu, Fedora/RHEL, and Arch where they differ.

Install HAProxy

Debian / Ubuntu

The default apt repositories often carry an older HAProxy. Add the official PPA for the latest 2.x stable branch.

sudo add-apt-repository ppa:vbernat/haproxy-2.9
sudo apt update
sudo apt install haproxy

Fedora / RHEL 9 / Rocky Linux

sudo dnf install haproxy

Arch Linux

sudo pacman -S haproxy

Enable and start the service with systemd:

sudo systemctl enable --now haproxy

Verify the version you landed on:

haproxy -v

Output will resemble: HAProxy version 2.9.x .... Anything below 2.4 is end-of-life; upgrade before putting it in front of production traffic.

Configuration File Structure

HAProxy's configuration lives at /etc/haproxy/haproxy.cfg. It is divided into four sections: global (process-level settings), defaults (inherited by all frontends and backends), frontend (where traffic enters), and backend (where it goes). Always validate the file before reloading:

sudo haproxy -c -f /etc/haproxy/haproxy.cfg

Frontend and Backend Basics

Below is a minimal but realistic configuration for HTTP load balancing across three application servers. Replace the IP addresses with your actual backend hosts.

global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    maxconn 50000

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  forwardfor
    option  http-server-close
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 503 /etc/haproxy/errors/503.http

frontend http_in
    bind *:80
    default_backend app_servers

backend app_servers
    balance roundrobin
    server app1 192.168.1.10:8080 check
    server app2 192.168.1.11:8080 check
    server app3 192.168.1.12:8080 check

The balance directive controls the algorithm. Common choices are roundrobin, leastconn (best for long-lived connections like WebSockets), and source (IP hash). The check keyword on each server line enables TCP-level health checks by default.

ACLs: Content-Based Routing

Access Control Lists let you route traffic based on headers, paths, source IPs, and more. A common pattern is splitting API traffic from the main web application.

frontend http_in
    bind *:80

    # Define ACLs
    acl is_api    path_beg /api/
    acl is_static path_end .css .js .png .jpg .svg .woff2

    # Route based on ACLs
    use_backend api_servers    if is_api
    use_backend static_servers if is_static
    default_backend app_servers

backend api_servers
    balance leastconn
    server api1 192.168.1.20:3000 check
    server api2 192.168.1.21:3000 check

backend static_servers
    balance roundrobin
    server cdn1 192.168.1.30:80 check

backend app_servers
    balance roundrobin
    server app1 192.168.1.10:8080 check
    server app2 192.168.1.11:8080 check

ACL conditions can be combined with if / unless and Boolean operators. For example, use_backend admin_backend if is_admin_path !is_external_ip routes to an admin backend only when the path matches and the source is internal.

TLS Termination

HAProxy terminates TLS in the frontend, decrypting traffic before passing plain HTTP to your backends. You need a combined PEM file containing the certificate, any intermediates, and the private key in that order.

sudo cat /etc/ssl/certs/example.com.crt \
         /etc/ssl/certs/ca-bundle.crt \
         /etc/ssl/private/example.com.key \
  | sudo tee /etc/haproxy/certs/example.com.pem
sudo chmod 640 /etc/haproxy/certs/example.com.pem
sudo chown root:haproxy /etc/haproxy/certs/example.com.pem

Update the frontend to bind on port 443 and reference the PEM file. Also add an HTTP-to-HTTPS redirect on port 80:

frontend http_redirect
    bind *:80
    http-request redirect scheme https code 301

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11

    http-response set-header Strict-Transport-Security "max-age=63072000"

    acl is_api path_beg /api/
    use_backend api_servers if is_api
    default_backend app_servers

The cipher suite list above enforces TLS 1.3 only. If you need TLS 1.2 compatibility, add no-tls13 removal and extend ssl-default-bind-ciphers with a TLS 1.2 suite list. Use haproxy -vv | grep OpenSSL to confirm which OpenSSL version and features are compiled in.

Sticky Sessions

Some applications store session state locally on the backend server. Sticky sessions ensure repeat requests from the same client always reach the same server. HAProxy implements this via cookies.

backend app_servers
    balance roundrobin
    cookie SERVERID insert indirect nocache
    server app1 192.168.1.10:8080 check cookie app1
    server app2 192.168.1.11:8080 check cookie app2
    server app3 192.168.1.12:8080 check cookie app3

insert tells HAProxy to inject its own SERVERID cookie if the client does not already carry one. indirect strips the cookie before forwarding the request so your application never sees it. nocache prevents proxies from caching responses with this cookie. If a backend marked with a particular cookie value goes down, HAProxy fails the request to another server—it does not serve a 503 just because the sticky target is unhealthy.

Health Checks

The bare check keyword performs a TCP connect. For HTTP services you want a real application-level health check:

backend app_servers
    balance roundrobin
    option httpchk GET /healthz HTTP/1.1\r\nHost:\ app.internal
    http-check expect status 200
    default-server inter 5s fall 3 rise 2
    server app1 192.168.1.10:8080 check
    server app2 192.168.1.11:8080 check
    server app3 192.168.1.12:8080 check
  • inter 5s — check every 5 seconds.
  • fall 3 — mark a server down after 3 consecutive failures.
  • rise 2 — return it to rotation after 2 consecutive successes.
  • http-check expect status 200 — any non-200 response marks the check failed.

You can also use http-check expect rstring OK to match a regex in the response body if your health endpoint returns a JSON payload.

Stats Dashboard

HAProxy ships a built-in statistics page. Add a dedicated frontend for it:

frontend stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:changeme
    stats hide-version

After reloading, visit http://your-server:8404/stats. Protect this with firewall rules or bind to a management interface only—never expose it to the public internet with a weak password.

Apply Configuration and Verify

# Validate first
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Reload without dropping connections (graceful)
sudo systemctl reload haproxy

# Check status
sudo systemctl status haproxy

Use reload, not restart. HAProxy supports hitless reloads via socket-passing; restarting drops in-flight connections.

Test TLS from the command line:

openssl s_client -connect your-server:443 -servername example.com < /dev/null 2>&1 | grep -E 'Protocol|Cipher'

Troubleshooting

  • Permission denied on the stats socket: Ensure your user is in the haproxy group, or use sudo socat stdio /run/haproxy/admin.sock.
  • Backend shows DOWN immediately: Run sudo journalctl -u haproxy -n 50 and look for connection refused errors. Confirm the backend port is actually listening with ss -tlnp on the backend host.
  • TLS handshake failure: Check that your PEM file contains the full chain. openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/haproxy/certs/example.com.pem should return OK.
  • Sticky sessions not holding: Confirm the client is accepting and sending cookies. Use curl -c cookiejar.txt -b cookiejar.txt https://example.com/ to simulate. Also verify indirect is set so the cookie survives the round trip.
  • Config validation passes but reload fails: Check available file descriptors. HAProxy can exhaust them under high maxconn. Raise the system limit in /etc/security/limits.conf or via a systemd override (LimitNOFILE=1000000).
tested on:Ubuntu 24.04Debian 12Rocky 9Arch rolling

Frequently asked questions

What is the difference between HAProxy's frontend and backend?
A frontend defines where HAProxy listens for incoming connections and which rules apply to arriving traffic. A backend defines the pool of servers that actually handle requests. One frontend can route to multiple backends using ACLs.
Can HAProxy terminate TLS and also pass raw TCP through to the backend?
Yes. Set mode tcp in the frontend and use 'pass-through' (no ssl crt directive) to forward the encrypted stream untouched. This is useful when your application servers handle TLS themselves, but you lose the ability to inspect HTTP headers.
How do I reload HAProxy without dropping active connections?
Use 'systemctl reload haproxy' instead of restart. HAProxy passes the listening sockets to the new process and drains old connections gracefully, achieving a hitless reload.
Are sticky sessions reliable when a backend server fails?
No. When the target backend is marked down, HAProxy will route the client to another server, breaking session affinity. Sticky sessions are not a substitute for proper server-side session sharing via Redis, a database, or a distributed cache.
Does HAProxy support HTTP/2?
Yes, from version 2.0 onwards. Add 'alpn h2,http/1.1' to the bind line on your HTTPS frontend to negotiate HTTP/2 via ALPN. Backend connections are typically HTTP/1.1 unless you explicitly configure h2 on the server lines.

Related guides