HAProxy is the workhorse of modern web infrastructure — a fast, reliable load balancer and proxy that powers millions of production deployments. Unlike Nginx or Traefik, HAProxy is purpose-built for load balancing, giving it surgical control over how traffic flows across your backend pool. If you’re running more than one application server, HAProxy is almost certainly the right tool for the job.

Installing HAProxy

# Ubuntu / Debian
$ sudo apt update && sudo apt install haproxy

# RHEL / CentOS / Amazon Linux
$ sudo yum install haproxy

# Verify the version
$ haproxy -v
HAProxy version 2.8.3 2023/08/11

The main config file lives at /etc/haproxy/haproxy.cfg.

Config Structure

Every HAProxy config is made up of four sections:

global      # Process-level settings (daemon mode, logging, ulimits)
defaults    # Default values applied to all frontends and backends
frontend    # Listens for incoming connections
backend     # Defines the pool of servers to forward to

A minimal working config looks like this:

global
    log /dev/log local0
    maxconn 50000
    daemon

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5s
    timeout client  30s
    timeout server  30s

frontend web
    bind *:80
    default_backend app_servers

backend app_servers
    balance roundrobin
    server app1 10.0.0.1:8080 check
    server app2 10.0.0.2:8080 check
    server app3 10.0.0.3:8080 check

Frontend Configuration

The frontend is where HAProxy receives connections. You can bind to multiple ports, apply ACLs, and route to different backends.

frontend web
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/myapp.pem

    # Redirect HTTP to HTTPS
    redirect scheme https if !{ ssl_fc }

    # Route by hostname
    acl is_api hdr(host) -i api.example.com
    use_backend api_servers if is_api
    default_backend web_servers

Backend Configuration

The backend defines the server pool and how HAProxy distributes traffic.

backend web_servers
    balance leastconn
    option httpchk GET /health
    http-check expect status 200
    server web1 10.0.0.1:8080 check inter 10s rise 2 fall 3
    server web2 10.0.0.2:8080 check inter 10s rise 2 fall 3
    server web3 10.0.0.3:8080 check inter 10s rise 2 fall 3 weight 2

check enables health checking. inter sets the check interval. rise and fall set how many successful/failed checks change the server’s state.

Load Balancing Algorithms

Algorithm Description
roundrobin Distributes requests evenly in rotation
leastconn Sends to the server with fewest active connections
source Hashes client IP — same client always hits same server
uri Hashes the request URI — same URL always hits same server
random Random server selection

leastconn is usually best for long-lived connections (databases, WebSockets). roundrobin is fine for stateless HTTP.

Health Checks

HAProxy can check backend health at the TCP or HTTP level:

backend api_servers
    option httpchk GET /ping HTTP/1.1\r\nHost:\ api.example.com
    http-check expect status 200
    server api1 10.0.0.10:3000 check
    server api2 10.0.0.11:3000 check

For TCP-level checks (databases, non-HTTP services):

backend db_servers
    option tcp-check
    server db1 10.0.0.20:5432 check

ACLs and Routing

ACLs let you route, block, or rewrite traffic based on headers, paths, IPs, and more:

frontend web
    bind *:80

    # Define ACLs
    acl is_api path_beg /api/
    acl is_static path_end .jpg .png .css .js
    acl internal_ip src 10.0.0.0/8

    # Block everything except internal IPs on the admin path
    acl is_admin path_beg /admin
    http-request deny if is_admin !internal_ip

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

SSL Termination

HAProxy terminates SSL at the frontend. Combine your certificate and private key into a single .pem file:

$ cat server.crt server.key > /etc/ssl/certs/myapp.pem

Then configure the frontend:

frontend web_ssl
    bind *:443 ssl crt /etc/ssl/certs/myapp.pem
    bind *:80
    redirect scheme https if !{ ssl_fc }

    # Add standard security headers
    http-response set-header Strict-Transport-Security "max-age=63072000"
    http-response set-header X-Frame-Options SAMEORIGIN

    default_backend app_servers

Pass the real client IP to your backends:

backend app_servers
    option forwardfor
    http-request set-header X-Forwarded-Proto https
    server app1 10.0.0.1:8080 check

Stats Page

HAProxy has a built-in stats dashboard. Enable it by adding a dedicated frontend:

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

Visit http://your-server:8404/stats to see real-time connection counts, server states, and request rates.

Validating and Reloading Config

# Validate config without restarting
$ haproxy -c -f /etc/haproxy/haproxy.cfg
Configuration file is valid

# Reload gracefully (keeps active connections alive)
$ sudo systemctl reload haproxy

# Full restart (drops active connections)
$ sudo systemctl restart haproxy

Quick Cheat Sheet

# Frontend directives
bind *:80                           # Listen on port 80
bind *:443 ssl crt /path/to.pem    # Listen with SSL
default_backend name                # Send to this backend by default
use_backend name if acl_name        # Conditional routing

# Backend directives
balance roundrobin                  # Load balancing algorithm
server name ip:port check           # Add a server with health check
option httpchk GET /health          # HTTP health check endpoint
timeout server 30s                  # Server response timeout

# ACL operators
hdr(host) -i example.com           # Match Host header (case insensitive)
path_beg /api                       # Path starts with
path_end .jpg                       # Path ends with
src 10.0.0.0/8                     # Source IP range

Conclusion

HAProxy’s configuration is verbose compared to Nginx, but that verbosity is its strength — every behavior is explicit and tunable. Start with the four-section structure (global, defaults, frontend, backend), pick a load balancing algorithm that matches your workload, enable health checks, and use ACLs for any routing logic beyond simple default routing. The stats page alone is worth the setup time for production visibility.