HAProxy (High Availability Proxy) is a production-grade, open-source TCP/HTTP load balancer and proxy server. It is widely used to distribute traffic across multiple backend servers, improving reliability and performance for web applications, APIs, and database clusters. HAProxy handles millions of concurrent connections with minimal resource usage, making it a go-to choice for high-traffic environments.

In this guide, you will install and configure HAProxy on Debian 13 (Trixie) or Debian 12 (Bookworm). We will cover installation from Debian repos and the official HAProxy PPA, build a complete configuration from scratch, set up HTTP and TCP load balancing, SSL termination with Let’s Encrypt, a stats dashboard, logging with rsyslog, and troubleshooting.

Prerequisites

Before you begin, confirm you have the following in place:

  • A Debian 13 or Debian 12 server with root or sudo access
  • At least two backend servers (web servers or application servers) to load balance
  • A domain name pointing to your HAProxy server (required for SSL termination)
  • Ports 80 and 443 open and not used by another service on the HAProxy server
  • Basic familiarity with Linux command line and text editors

Step 1: Update System Packages

Start by updating the package index and upgrading installed packages to their latest versions:

sudo apt update && sudo apt upgrade -y

Verify your Debian version:

cat /etc/os-release | grep -i pretty

You should see output showing Debian 13 (Trixie) or Debian 12 (Bookworm).

Step 2: Install HAProxy on Debian 13/12

You have two options for installation – the Debian repository version or the official HAProxy PPA for the latest 3.x release.

Option 1: Install HAProxy from Debian Repositories

The default Debian repositories include a stable HAProxy package. This is the simplest method:

sudo apt install -y haproxy

Check the installed version:

haproxy -v

On Debian 12, this installs HAProxy 2.6.x. On Debian 13, you get HAProxy 2.9.x or newer. If you need the latest 3.x features, use Option 2 instead.

Option 2: Install HAProxy 3.x from Official PPA

To get the latest HAProxy 3.x release with the newest features and performance improvements, add the official HAProxy PPA maintained by the HAProxy team:

sudo apt install -y software-properties-common

Add the official HAProxy repository. For Debian 12 (Bookworm):

echo "deb [signed-by=/usr/share/keyrings/haproxy.debian.net.gpg] http://haproxy.debian.net bookworm-backports-3.0 main" | sudo tee /etc/apt/sources.list.d/haproxy.list
curl -fsSL https://haproxy.debian.net/bernat.debian.org.gpg | sudo gpg --dearmor -o /usr/share/keyrings/haproxy.debian.net.gpg

For Debian 13 (Trixie):

echo "deb [signed-by=/usr/share/keyrings/haproxy.debian.net.gpg] http://haproxy.debian.net trixie-backports-3.0 main" | sudo tee /etc/apt/sources.list.d/haproxy.list
curl -fsSL https://haproxy.debian.net/bernat.debian.org.gpg | sudo gpg --dearmor -o /usr/share/keyrings/haproxy.debian.net.gpg

Now update the package index and install HAProxy 3.x:

sudo apt update
sudo apt install -y haproxy=3.0.\*

Verify the installation:

haproxy -v

Expected output should show HAProxy version 3.0.x.

Step 3: Understand HAProxy Configuration Structure

The main HAProxy configuration file is /etc/haproxy/haproxy.cfg. It is divided into four main sections:

  • global – Process-wide settings like logging, max connections, and user/group
  • defaults – Default parameters applied to all frontend and backend sections unless overridden
  • frontend – Defines how incoming client requests are received (bind addresses, ports, ACLs, routing rules)
  • backend – Defines the pool of servers that handle the requests (server addresses, health checks, balancing algorithms)

Let’s back up the default configuration before making changes:

sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak

Step 4: Configure HAProxy for HTTP Load Balancing

Open the configuration file with your preferred editor:

sudo nano /etc/haproxy/haproxy.cfg

Replace the contents with the following configuration. Adjust the backend server IPs and ports to match your environment:

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

    # SSL tuning
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

    # Max connections
    maxconn 50000

#---------------------------------------------------------------------
# Default settings
#---------------------------------------------------------------------
defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  forwardfor
    option  http-server-close
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    timeout http-request 10s
    timeout http-keep-alive 10s
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

#---------------------------------------------------------------------
# Frontend - HTTP
#---------------------------------------------------------------------
frontend http_front
    bind *:80
    default_backend http_back

    # Optional: redirect all HTTP to HTTPS
    # http-request redirect scheme https unless { ssl_fc }

#---------------------------------------------------------------------
# Backend - HTTP web servers
#---------------------------------------------------------------------
backend http_back
    balance roundrobin
    option httpchk GET /
    http-check expect status 200

    server web1 192.168.1.10:80 check inter 5s rise 3 fall 3
    server web2 192.168.1.11:80 check inter 5s rise 3 fall 3
    server web3 192.168.1.12:80 check inter 5s rise 3 fall 3

Here is what the key directives mean:

  • balance roundrobin – Distributes requests evenly across backend servers in rotation
  • option httpchk GET / – Sends an HTTP GET request to / on each backend to verify it is alive
  • http-check expect status 200 – Expects a 200 OK response for the health check to pass
  • check inter 5s rise 3 fall 3 – Checks every 5 seconds; marks a server as up after 3 consecutive successes or down after 3 consecutive failures
  • option forwardfor – Adds the X-Forwarded-For header so backend servers see the real client IP

Validate the configuration syntax before restarting:

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

You should see: Configuration file is valid

Step 5: Configure TCP Load Balancing (Database Example)

HAProxy can also load balance TCP connections – useful for databases like MySQL, PostgreSQL, or Redis. Add the following sections to your haproxy.cfg file (or create a separate config file):

#---------------------------------------------------------------------
# Frontend - TCP for MySQL
#---------------------------------------------------------------------
frontend mysql_front
    bind *:3306
    mode tcp
    option tcplog
    default_backend mysql_back

#---------------------------------------------------------------------
# Backend - MySQL servers
#---------------------------------------------------------------------
backend mysql_back
    mode tcp
    balance leastconn
    option mysql-check user haproxy_check

    server mysql1 192.168.1.20:3306 check inter 5s rise 3 fall 3
    server mysql2 192.168.1.21:3306 check inter 5s rise 3 fall 3 backup

Key differences for TCP load balancing:

  • mode tcp – Operates at Layer 4 (transport layer) instead of Layer 7 (application layer)
  • option tcplog – Logs TCP connection details instead of HTTP request details
  • balance leastconn – Routes new connections to the server with the fewest active connections – ideal for long-lived database connections
  • option mysql-check – Performs a MySQL-specific health check using the specified user
  • backup – Marks the server as a standby that only receives traffic when all primary servers are down

For the MySQL health check to work, create a monitoring user on your MySQL servers:

CREATE USER 'haproxy_check'@'192.168.1.%';
FLUSH PRIVILEGES;

For PostgreSQL TCP load balancing, replace the MySQL section with:

frontend pgsql_front
    bind *:5432
    mode tcp
    option tcplog
    default_backend pgsql_back

backend pgsql_back
    mode tcp
    balance leastconn
    option pgsql-check user haproxy_check

    server pgsql1 192.168.1.30:5432 check inter 5s rise 3 fall 3
    server pgsql2 192.168.1.31:5432 check inter 5s rise 3 fall 3

Step 6: Configure SSL Termination with Let’s Encrypt

SSL termination at the load balancer means HAProxy handles encryption/decryption, and traffic between HAProxy and backends travels over plain HTTP. This offloads CPU work from your backend servers.

Install Certbot

sudo apt install -y certbot

Obtain the Certificate

Stop HAProxy temporarily so Certbot can bind to port 80:

sudo systemctl stop haproxy
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com

Replace yourdomain.com with your actual domain name.

Prepare the Certificate for HAProxy

HAProxy requires the certificate and private key combined in a single PEM file:

sudo mkdir -p /etc/haproxy/certs

DOMAIN="yourdomain.com"
sudo cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem \
    /etc/letsencrypt/live/$DOMAIN/privkey.pem \
    | sudo tee /etc/haproxy/certs/$DOMAIN.pem > /dev/null

sudo chmod 600 /etc/haproxy/certs/$DOMAIN.pem

Update HAProxy Configuration for SSL

Modify the frontend section in /etc/haproxy/haproxy.cfg:

#---------------------------------------------------------------------
# Frontend - HTTPS with SSL termination
#---------------------------------------------------------------------
frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/yourdomain.com.pem
    bind *:80

    # Redirect HTTP to HTTPS
    http-request redirect scheme https unless { ssl_fc }

    # HSTS header
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    default_backend http_back

Auto-Renew Certificates

Create a renewal script that combines the certificate files and reloads HAProxy:

sudo nano /etc/letsencrypt/renewal-hooks/post/haproxy-ssl-renew.sh

Add the following content:

#!/bin/bash
DOMAIN="yourdomain.com"
cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem \
    /etc/letsencrypt/live/$DOMAIN/privkey.pem \
    > /etc/haproxy/certs/$DOMAIN.pem
systemctl reload haproxy

Make the script executable:

sudo chmod +x /etc/letsencrypt/renewal-hooks/post/haproxy-ssl-renew.sh

Test the renewal process (dry run):

sudo certbot renew --dry-run

Step 7: Enable the HAProxy Stats Dashboard

The HAProxy stats page gives you a real-time web-based view of frontend/backend health, connection counts, and traffic rates. Add this section to /etc/haproxy/haproxy.cfg:

#---------------------------------------------------------------------
# Stats Dashboard
#---------------------------------------------------------------------
listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s
    stats show-legends
    stats admin if LOCALHOST
    stats auth admin:YourSecurePassword

Replace YourSecurePassword with a strong password. The stats page will be available at http://your-server-ip:8404/stats.

Key directives explained:

  • stats uri /stats – The URL path for the dashboard
  • stats refresh 10s – Auto-refreshes the page every 10 seconds
  • stats admin if LOCALHOST – Allows admin actions (enable/disable servers) only from localhost
  • stats auth – Requires username and password for access

Step 8: Configure Logging with Rsyslog

By default, HAProxy logs to /dev/log using syslog. To get proper log files, configure rsyslog to handle HAProxy log messages.

Create a dedicated rsyslog configuration file:

sudo nano /etc/rsyslog.d/49-haproxy.conf

Add the following content:

# Collect HAProxy logs
local0.* /var/log/haproxy/haproxy.log
local1.* /var/log/haproxy/haproxy-notice.log

# Prevent HAProxy messages from cluttering /var/log/syslog
& stop

Create the log directory:

sudo mkdir -p /var/log/haproxy

Restart rsyslog to apply the changes:

sudo systemctl restart rsyslog

Verify that the rsyslog UDP listener is enabled. Open /etc/rsyslog.conf and make sure these lines are uncommented:

module(load="imudp")
input(type="imudp" port="514")

If you made changes to /etc/rsyslog.conf, restart rsyslog again:

sudo systemctl restart rsyslog

To set up log rotation so log files do not grow indefinitely, create a logrotate configuration:

sudo nano /etc/logrotate.d/haproxy

Add the following:

/var/log/haproxy/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    postrotate
        /usr/lib/rsyslog/rsyslog-rotate
    endscript
}

Step 9: Enable and Start HAProxy

Validate the configuration one more time:

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

Enable HAProxy to start automatically at boot and start the service:

sudo systemctl enable haproxy
sudo systemctl start haproxy

Verify the service is running:

sudo systemctl status haproxy

You should see active (running) in the output. Confirm HAProxy is listening on the expected ports:

sudo ss -tlnp | grep haproxy

Expected output should show HAProxy listening on ports 80, 443, and 8404 (or whichever ports you configured).

Step 10: Configure Firewall Rules

If you are running ufw (Uncomplicated Firewall), open the required ports:

sudo apt install -y ufw
sudo ufw allow 80/tcp comment "HAProxy HTTP"
sudo ufw allow 443/tcp comment "HAProxy HTTPS"
sudo ufw allow 8404/tcp comment "HAProxy Stats"
sudo ufw enable
sudo ufw status verbose

If you are using nftables directly (the default firewall framework on Debian 12/13), add rules like this:

sudo nft add rule inet filter input tcp dport {80, 443, 8404} accept

If you configured TCP load balancing for MySQL (port 3306) or PostgreSQL (port 5432), open those ports as well – but restrict access to trusted source IPs only:

# UFW example - restrict MySQL port to specific subnet
sudo ufw allow from 192.168.1.0/24 to any port 3306 proto tcp comment "MySQL via HAProxy"

Step 11: Test the Setup

Test with curl

From the HAProxy server or another machine, test HTTP load balancing:

curl -I http://your-server-ip

You should receive an HTTP response from one of your backend servers. Run the command multiple times and observe the responses to confirm round-robin distribution.

Test HTTPS if you configured SSL termination:

curl -I https://yourdomain.com

Verify the redirect from HTTP to HTTPS:

curl -I http://yourdomain.com

You should see a 301 or 302 redirect to the HTTPS version.

Test the Stats Dashboard

Open your browser and navigate to:

http://your-server-ip:8404/stats

Enter the username and password you configured in the stats section. You will see a dashboard showing all frontends, backends, and individual server status with traffic statistics.

Test Health Checks

Stop one of your backend web servers and watch the stats dashboard. Within 15 seconds (3 failed checks at 5-second intervals), the server should transition from green (UP) to red (DOWN). HAProxy will stop sending traffic to that server automatically.

You can also check the HAProxy logs:

sudo tail -f /var/log/haproxy/haproxy.log

Complete Configuration Reference

Here is a full /etc/haproxy/haproxy.cfg example that combines HTTP load balancing, SSL termination, stats dashboard, and logging:

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

    # SSL settings
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

#---------------------------------------------------------------------
# Default settings
#---------------------------------------------------------------------
defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  forwardfor
    option  http-server-close
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    timeout http-request 10s
    timeout http-keep-alive 10s
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

#---------------------------------------------------------------------
# Frontend - HTTPS with SSL termination
#---------------------------------------------------------------------
frontend https_front
    bind *:443 ssl crt /etc/haproxy/certs/yourdomain.com.pem
    bind *:80

    # Redirect HTTP to HTTPS
    http-request redirect scheme https unless { ssl_fc }

    # Security headers
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    http-response set-header X-Frame-Options "SAMEORIGIN"
    http-response set-header X-Content-Type-Options "nosniff"

    default_backend http_back

#---------------------------------------------------------------------
# Backend - Web servers
#---------------------------------------------------------------------
backend http_back
    balance roundrobin
    option httpchk GET /
    http-check expect status 200
    cookie SERVERID insert indirect nocache

    server web1 192.168.1.10:80 check inter 5s rise 3 fall 3 cookie web1
    server web2 192.168.1.11:80 check inter 5s rise 3 fall 3 cookie web2
    server web3 192.168.1.12:80 check inter 5s rise 3 fall 3 cookie web3

#---------------------------------------------------------------------
# Stats Dashboard
#---------------------------------------------------------------------
listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s
    stats show-legends
    stats admin if LOCALHOST
    stats auth admin:YourSecurePassword

Troubleshooting HAProxy on Debian 13/12

Here are common issues and how to resolve them.

HAProxy fails to start

Check the configuration for syntax errors:

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

Check the systemd journal for detailed error messages:

sudo journalctl -u haproxy -n 50 --no-pager

Port already in use

If HAProxy cannot bind to a port, find what process is using it:

sudo ss -tlnp | grep ':80'

Stop the conflicting service (commonly Apache or Nginx) or change the HAProxy bind port.

All backends showing DOWN in stats

Verify your backend servers are reachable from the HAProxy server:

curl -I http://192.168.1.10:80

Check that the health check path returns a 200 status. If your application uses a different health endpoint, update the option httpchk directive accordingly:

option httpchk GET /health
http-check expect status 200

No logs appearing in /var/log/haproxy/

Confirm that rsyslog is running and the HAProxy rsyslog configuration is in place:

sudo systemctl status rsyslog
cat /etc/rsyslog.d/49-haproxy.conf

Make sure the UDP listener is enabled in /etc/rsyslog.conf and restart rsyslog:

sudo systemctl restart rsyslog

SSL certificate errors

Verify the combined PEM file contains both the certificate chain and the private key:

sudo openssl x509 -in /etc/haproxy/certs/yourdomain.com.pem -text -noout | head -15

Make sure the PEM file has the correct permissions:

sudo chmod 600 /etc/haproxy/certs/yourdomain.com.pem
sudo chown root:root /etc/haproxy/certs/yourdomain.com.pem

Connection timeouts

If clients experience timeouts, increase the relevant timeout values in the defaults section:

timeout connect 10s
timeout client  60s
timeout server  60s

For applications that use WebSockets or long-polling, you need tunnel timeouts:

timeout tunnel 3600s

Reload without downtime

When you make configuration changes, use reload instead of restart to avoid dropping active connections:

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

This validates the config first, then performs a graceful reload only if the config is valid.

Conclusion

You have installed and configured HAProxy on Debian 13/12 with HTTP load balancing, TCP (database) load balancing, SSL termination using Let’s Encrypt, a stats dashboard for monitoring, and proper logging with rsyslog. HAProxy is now distributing traffic across your backend servers with health checks ensuring only healthy servers receive requests.

For production environments, consider adding rate limiting with stick-table directives, tuning maxconn based on your server resources, and setting up monitoring with Prometheus via the built-in HAProxy Prometheus exporter endpoint.

LEAVE A REPLY

Please enter your comment!
Please enter your name here