HAProxy is famous as an HTTP load balancer, but it’s equally good at layer 4 (TCP). In TCP mode it forwards raw connections without understanding the protocol inside. That’s ideal for databases, Redis, mail, game servers, MQTT, or TLS traffic you don’t want to decrypt. This guide covers TCP-mode setups with health checks, SNI-based routing and the PROXY protocol.

For HTTP load balancing, see HAProxy load balancer.

Install

sudo apt install haproxy        # Debian/Ubuntu
sudo dnf install haproxy        # RHEL family
haproxy -v

Config: /etc/haproxy/haproxy.cfg. Validate before reloading:

sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy

Basic TCP proxy

Forward port 5432 on this host to a PostgreSQL server on a private network:

defaults
    mode tcp
    log global
    option tcplog
    timeout connect 5s
    timeout client  1h
    timeout server  1h

frontend pg_in
    bind 10.0.0.5:5432
    default_backend pg_servers

backend pg_servers
    server db1 10.0.0.12:5432 check

Long timeout client and timeout server values matter for database connections, which can sit idle in connection pools. HTTP-style short timeouts would cut them.

Load balancing with health checks

backend redis_replicas
    balance roundrobin
    option tcp-check
    tcp-check connect
    server r1 10.0.0.21:6379 check inter 2s fall 3 rise 2
    server r2 10.0.0.22:6379 check inter 2s fall 3 rise 2
  • check enables health checks. inter is the interval, fall/rise are the failures and successes needed to change state.
  • balance roundrobin, leastconn (good for long-lived connections) or source (sticky by client IP).

Protocol-aware checks

Simple TCP checks only prove the port is open. HAProxy can do better.

Redis: only send traffic to the master:

backend redis_master
    option tcp-check
    tcp-check send PING\r\n
    tcp-check expect string +PONG
    tcp-check send info\ replication\r\n
    tcp-check expect string role:master
    tcp-check send QUIT\r\n
    server r1 10.0.0.21:6379 check
    server r2 10.0.0.22:6379 check

MySQL: option mysql-check user haproxy (create a passwordless haproxy user allowed only from the HAProxy IP, with no privileges).

PostgreSQL: option pgsql-check user haproxy. For primary/replica awareness, an HTTP check against Patroni’s REST API (option httpchk GET /primary on port 8008) is the common pattern.

See database replication and PgBouncer.

SNI routing: TLS passthrough to different backends

Route TLS connections by hostname without decrypting them. Each backend keeps its own certificates:

frontend tls_in
    mode tcp
    bind :443
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

    use_backend app1 if { req_ssl_sni -i app1.example.com }
    use_backend app2 if { req_ssl_sni -i app2.example.com }
    default_backend app1

backend app1
    mode tcp
    server s1 10.0.0.31:443 check

backend app2
    mode tcp
    server s2 10.0.0.32:443 check

This is useful when several services share one public IP and port 443, but must terminate TLS themselves (for example a mail web UI, or apps managing their own certificates).

Preserving the client IP: PROXY protocol

In TCP mode, backends see HAProxy’s IP as the client. The PROXY protocol prepends the original client address to the connection:

backend web
    mode tcp
    server s1 10.0.0.31:443 check send-proxy-v2

The backend must expect it, or it sees garbage. In Nginx:

server {
    listen 443 ssl proxy_protocol;
    set_real_ip_from 10.0.0.5;
    real_ip_header proxy_protocol;
}

Postfix, Dovecot, many game servers and HAProxy itself support it too.

Stats and monitoring

listen stats
    mode http
    bind 127.0.0.1:8404
    stats enable
    stats uri /stats
    stats refresh 10s

Open it through an SSH tunnel (ssh -L 8404:127.0.0.1:8404 host). It shows backend states, connections and errors. HAProxy also exposes Prometheus metrics. See Prometheus and Grafana.

High availability for HAProxy itself

One HAProxy is a single point of failure. Run two, with a floating IP managed by keepalived (VRRP). See keepalived floating IP.

Security

  • Bind frontends to private addresses where only internal clients connect (bind 10.0.0.5:5432), and firewall them.
  • Never expose database frontends publicly. See check open ports.
  • Use maxconn limits per frontend or server to protect backends.

Quick reference

Task Config
TCP mode mode tcp
Long-lived connections timeout client/server 1h
Health check server … check inter 2s fall 3 rise 2
Least connections balance leastconn
SNI routing req_ssl_sni + tcp-request inspect-delay
Client IP to backend send-proxy-v2 + backend proxy_protocol
Validate haproxy -c -f /etc/haproxy/haproxy.cfg

Related: Nginx load balancing and port forwarding.

  • HAProxy
  • Load Balancing
  • Networking