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-Forheader 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.





























































