Server

HAProxy Basics

By Prabath Thalangama· October 24, 2026· 4 min read
#haproxy#load-balancing#networking

Introduction

HAProxy sits in front of your servers and distributes connections. It does L4 (TCP) or L7 (HTTP) load balancing, TLS termination, health checking, and content-based routing, all from one config file.

The model

frontend  -> receives client connections (a bind address + port)
   |  (ACLs decide which backend)
backend   -> a pool of servers + how to pick one + how to health-check them

A working config

global
    log /dev/log local0
    maxconn 20000
    stats socket /run/haproxy/admin.sock mode 660 level admin

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

frontend fe_web
    bind :80
    bind :443 ssl crt /etc/haproxy/certs/site.pem alpn h2,http/1.1
    http-request redirect scheme https unless { ssl_fc }
    http-request set-header X-Forwarded-Proto https if { ssl_fc }

    acl is_api  path_beg /api/
    acl is_static path_end .jpg .css .js .png .woff2

    use_backend be_api    if is_api
    use_backend be_static if is_static
    default_backend be_app

backend be_app
    balance roundrobin
    option httpchk GET /healthz
    http-check expect status 200
    cookie SRVID insert indirect nocache
    server app1 10.0.1.11:8080 check cookie app1
    server app2 10.0.1.12:8080 check cookie app2

backend be_api
    balance leastconn
    option httpchk GET /api/health
    server api1 10.0.2.11:3000 check
    server api2 10.0.2.12:3000 check

backend be_static
    balance roundrobin
    server cdn1 10.0.3.11:80 check

listen stats
    bind :8404
    stats enable
    stats uri /
    stats refresh 10s
    stats auth admin:changeme

Key concepts

  • mode http vs mode tcp — L7 lets you route by path/host/header, rewrite, and terminate TLS. L4 is faster and protocol-agnostic (databases, SMTP, raw TCP). Set per frontend/backend.
  • balance — roundrobin (weighted, default), leastconn (fewest active connections — good for long-lived/uneven requests), source (hash client IP — crude stickiness), uri / hdr(...) (cache affinity), random.
  • Health checks — check on a server enables it; option httpchk + http-check expect makes it an HTTP check instead of a bare TCP connect. Also check inter 2s fall 3 rise 2 for timing.
  • Sticky sessions — cookie SRVID insert adds a cookie naming the server; subsequent requests from that client go back to it. Prefer stateless apps; use stickiness only when you must.
  • ACLs — named boolean conditions (path_beg, hdr(host) -i example.com, src 10.0.0.0/8, ssl_fc) combined in use_backend ... if.

TLS

  • Terminate: bind :443 ssl crt file.pem — HAProxy decrypts, backends get plain HTTP (or re-encrypt with server ... ssl).
  • The .pem is the fullchain cert + private key concatenated. A directory of .pems with crt /etc/haproxy/certs/ does SNI selection.
  • Modern TLS: ssl-default-bind-ciphersuites, ssl-min-ver TLSv1.2.

Verification and troubleshooting

haproxy -c -f /etc/haproxy/haproxy.cfg          # config check
echo "show stat" | socat - /run/haproxy/admin.sock | column -s, -t
echo "show servers state" | socat - /run/haproxy/admin.sock
# stats page: http://lb:8404/  (auth admin:changeme)

The stats page columns: Status (UP/DOWN/MAINT), LastChk (why a check failed), Sessions, Bytes, Denied, Errors, Warnings, Server.

  • All servers DOWN, "L7STS/404" or "L4CON" — the health check path 404s (wrong option httpchk URL), or the port/host is wrong (L4CON = can't even connect), or a backend firewall.
  • 503 Service Unavailable from HAProxy — no backend server is UP. Check the stats page LastChk.
  • 502 Bad Gateway — backend closed the connection / sent a malformed response, or timeout server too short for a slow endpoint.
  • 504 Gateway Timeout — timeout connect (can't reach backend) or timeout server (backend too slow).
  • Sticky sessions not sticking — client not sending cookies, or cookie directive missing on the server lines, or nocache interfering with a caching proxy.
  • Client IP lost at the backend — add option forwardfor (X-Forwarded-For) in mode http, or use the PROXY protocol (send-proxy + backend support) in mode tcp.
  • TLS handshake fails — cert/key not concatenated in the .pem, chain incomplete, or the client needs a cipher you disabled. openssl s_client -connect lb:443.
  • Reload drops connections — use haproxy -sf $(cat pid) / systemd's reload which does a seamless handover; hitless reloads keep existing connections.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.