Set up sophisticated traffic routing in HAProxy using Access Control Lists (ACLs) and map files for dynamic backend selection, SSL SNI routing, and intelligent request distribution based on headers, paths, and custom conditions.
Prerequisites
- HAProxy 2.4+
- Root or sudo access
- Basic understanding of HTTP headers
- SSL certificates for SNI routing
What this solves
HAProxy's advanced routing capabilities let you intelligently distribute traffic based on custom conditions like URL paths, HTTP headers, SSL SNI domains, or geographic regions. This tutorial covers Access Control Lists (ACLs) for complex routing logic and map files for dynamic backend selection without configuration reloads.
You'll need this when running multiple applications behind a single load balancer, implementing A/B testing, routing based on user agents, or managing multi-tenant applications with domain-based routing.
Step-by-step configuration
Install HAProxy
Install HAProxy and verify the version supports advanced ACL features.
sudo apt update
sudo apt install -y haproxysudo dnf install -y haproxyCreate map files directory
Create a directory for map files that will store dynamic routing rules.
sudo mkdir -p /etc/haproxy/maps
sudo chown haproxy:haproxy /etc/haproxy/maps
sudo chmod 755 /etc/haproxy/mapsCreate domain-to-backend map file
Create a map file that associates domains with specific backend servers for SNI routing.
# Domain to backend mapping
example.com web_backend
api.example.com api_backend
admin.example.com admin_backend
staging.example.com staging_backend
beta.example.com beta_backendCreate path-based routing map
Create a map file for path-based routing to different application backends.
# Path prefix to backend mapping
^/api/ api_backend
^/admin/ admin_backend
^/static/ static_backend
^/upload/ upload_backend
^/webhooks/ webhook_backendCreate user agent routing map
Create a map file for routing based on user agent strings for mobile/desktop separation.
# User agent patterns to backend mapping
Mobile mobile_backend
Android mobile_backend
iPhone mobile_backend
iPad mobile_backend
Bot bot_backend
curl api_backendCreate maintenance mode map
Create a map file for maintenance mode control per domain or path.
# Domain/path maintenance status
example.com false
api.example.com false
admin.example.com false
staging.example.com true
beta.example.com falseConfigure HAProxy with advanced ACLs
Create the main HAProxy configuration with sophisticated ACL rules and map file integration.
global
log stdout local0 info
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
ssl-default-bind-ciphers ECDHE+aRSA+AES256+GCM+SHA384:ECDHE+aRSA+CHACHA20:ECDHE+aRSA+AES128+GCM+SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
tune.ssl.default-dh-param 2048
defaults
mode http
log global
option httplog
option dontlognull
option log-health-checks
option forwardfor
option httpchk
timeout connect 5000
timeout client 50000
timeout server 50000
timeout http-request 10s
timeout http-keep-alive 2s
timeout check 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 web_frontend
bind *:80
bind *:443 ssl crt /etc/ssl/certs/haproxy/
# Redirect HTTP to HTTPS
redirect scheme https code 301 if !{ ssl_fc }
# Load map files
# Domain-based routing
acl is_domain_mapped map(/etc/haproxy/maps/domains.map) -m found
acl domain_backend map(/etc/haproxy/maps/domains.map) -m str
# Path-based routing
acl is_api_path path_beg /api/
acl is_admin_path path_beg /admin/
acl is_static_path path_beg /static/
acl is_upload_path path_beg /upload/
acl is_webhook_path path_beg /webhooks/
# User agent based routing
acl is_mobile_agent hdr_sub(User-Agent) -i -f /etc/haproxy/maps/useragent.map
acl is_bot_agent hdr_sub(User-Agent) -i bot crawler spider
acl is_curl_agent hdr_beg(User-Agent) -i curl
# Geographic and IP-based ACLs
acl is_internal_ip src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
acl is_office_ip src 203.0.113.0/24
acl is_cdn_ip src 198.51.100.0/24
# HTTP method and header ACLs
acl is_post_method method POST
acl is_get_method method GET
acl has_auth_header hdr(Authorization) -m found
acl is_json_content hdr(Content-Type) -i application/json
acl is_api_key_valid hdr(X-API-Key) -f /etc/haproxy/maps/valid_api_keys.map
# Time-based ACLs
acl is_business_hours date +%H %ge 9
acl is_business_hours date +%H %le 17
acl is_weekday date +%w %ge 1
acl is_weekday date +%w %le 5
acl is_working_hours is_business_hours is_weekday
# SSL SNI ACLs
acl is_api_sni ssl_fc_sni -i api.example.com
acl is_admin_sni ssl_fc_sni -i admin.example.com
acl is_main_sni ssl_fc_sni -i example.com www.example.com
# Maintenance mode checks
acl is_maintenance map_str(/etc/haproxy/maps/maintenance.map,%[ssl_fc_sni]) true
acl is_maintenance_path map_str(/etc/haproxy/maps/maintenance.map,%[path]) true
# Rate limiting ACLs
acl too_many_requests sc0_http_req_rate() gt 20
acl burst_detected sc0_http_req_rate() gt 50
# Security ACLs
acl blocked_ua hdr_sub(User-Agent) -i -f /etc/haproxy/blocked_agents.txt
acl blocked_ip src -f /etc/haproxy/blocked_ips.txt
acl suspicious_path path_reg -i \.(php|asp|jsp|cgi)$
acl sql_injection path_reg -i (union|select|insert|delete|drop|create|alter)
# Stick tables for rate limiting
stick-table type ip size 100k expire 30m store http_req_rate(10s)
http-request track-sc0 src
# Security rules (highest priority)
http-request deny if blocked_ip || blocked_ua || sql_injection
http-request deny if burst_detected
http-request tarpit if too_many_requests
# Maintenance mode
http-request redirect location /maintenance.html if is_maintenance !is_internal_ip
# Admin access restrictions
http-request deny if is_admin_path !is_office_ip !has_auth_header
http-request deny if is_admin_sni !is_office_ip
# API routing with authentication
use_backend api_backend if is_api_sni || is_api_path
use_backend api_backend if is_curl_agent is_api_key_valid
# Mobile traffic routing
use_backend mobile_backend if is_mobile_agent !is_api_path
# Bot traffic routing
use_backend bot_backend if is_bot_agent
# Static content routing
use_backend static_backend if is_static_path || path_end .css .js .png .jpg .gif .ico .woff2
# Upload service routing
use_backend upload_backend if is_upload_path is_post_method
# Webhook routing
use_backend webhook_backend if is_webhook_path is_json_content
# Admin panel routing
use_backend admin_backend if is_admin_sni || (is_admin_path is_office_ip)
# Domain-based routing using maps
use_backend %[ssl_fc_sni,map(/etc/haproxy/maps/domains.map)] if is_domain_mapped
# Working hours routing (different backend capacity)
use_backend web_backend_peak if is_working_hours
# Default backend
default_backend web_backend
# Backend definitions
backend web_backend
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
http-check expect status 200
server web1 203.0.113.10:80 check inter 5s fall 3 rise 2
server web2 203.0.113.11:80 check inter 5s fall 3 rise 2
server web3 203.0.113.12:80 check inter 5s fall 3 rise 2
backend web_backend_peak
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
http-check expect status 200
server web1 203.0.113.10:80 check inter 5s fall 3 rise 2
server web2 203.0.113.11:80 check inter 5s fall 3 rise 2
server web3 203.0.113.12:80 check inter 5s fall 3 rise 2
server web4 203.0.113.13:80 check inter 5s fall 3 rise 2
server web5 203.0.113.14:80 check inter 5s fall 3 rise 2
backend api_backend
balance leastconn
option httpchk GET /api/health HTTP/1.1\r\nHost:\ api.example.com
http-check expect status 200
http-request add-header X-Forwarded-Proto https
server api1 203.0.113.20:8080 check inter 3s fall 2 rise 2
server api2 203.0.113.21:8080 check inter 3s fall 2 rise 2
server api3 203.0.113.22:8080 check inter 3s fall 2 rise 2
backend mobile_backend
balance roundrobin
option httpchk GET /mobile/health HTTP/1.1\r\nHost:\ m.example.com
http-check expect status 200
compression algo gzip
compression type text/html text/css application/javascript
server mobile1 203.0.113.30:80 check inter 5s
server mobile2 203.0.113.31:80 check inter 5s
backend admin_backend
balance source
option httpchk GET /admin/health HTTP/1.1\r\nHost:\ admin.example.com
http-check expect status 200
http-request add-header X-Admin-Access true
server admin1 203.0.113.40:80 check inter 10s
server admin2 203.0.113.41:80 check inter 10s backup
backend static_backend
balance roundrobin
option httpchk GET /static/health.txt
http-check expect status 200
http-response set-header Cache-Control "public, max-age=3600"
server static1 203.0.113.50:80 check inter 30s
server static2 203.0.113.51:80 check inter 30s
server cdn1 203.0.113.60:80 check inter 30s backup
backend upload_backend
balance leastconn
option httpchk GET /upload/health HTTP/1.1\r\nHost:\ upload.example.com
http-check expect status 200
timeout server 300s
server upload1 203.0.113.70:80 check inter 10s
server upload2 203.0.113.71:80 check inter 10s
backend webhook_backend
balance roundrobin
option httpchk GET /webhooks/health HTTP/1.1\r\nHost:\ webhooks.example.com
http-check expect status 200
http-request add-header X-Webhook-Source haproxy
server webhook1 203.0.113.80:80 check inter 5s
server webhook2 203.0.113.81:80 check inter 5s
backend bot_backend
balance roundrobin
option httpchk GET /robots.txt
http-check expect status 200
rate-limit sessions 10
server bot1 203.0.113.90:80 check inter 30s
backend staging_backend
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ staging.example.com
http-check expect status 200
server staging1 203.0.113.100:80 check inter 10s
backend beta_backend
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ beta.example.com
http-check expect status 200
server beta1 203.0.113.110:80 check inter 10s
# Statistics interface
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUECreate API key validation map
Create a map file for valid API keys that can access protected endpoints.
# Valid API keys for authentication
sk-1234567890abcdef true
sk-fedcba0987654321 true
sk-api-key-production true
sk-api-key-staging trueCreate blocked agents and IPs files
Create files for blocking malicious user agents and IP addresses.
BadBot
MaliciousScanner
SQLInject
XSSAttack
Nikto
w3af
sqlmap198.51.100.100
198.51.100.101
203.0.113.200Set proper file permissions
Ensure HAProxy can read all configuration files and map files.
sudo chown haproxy:haproxy /etc/haproxy/maps/*
sudo chown haproxy:haproxy /etc/haproxy/blocked_*
sudo chmod 644 /etc/haproxy/maps/*
sudo chmod 644 /etc/haproxy/blocked_*
sudo chmod 644 /etc/haproxy/haproxy.cfgCreate SSL certificate directory
Create the directory structure for SSL certificates used in SNI routing.
sudo mkdir -p /etc/ssl/certs/haproxy
sudo chown haproxy:haproxy /etc/ssl/certs/haproxy
sudo chmod 700 /etc/ssl/certs/haproxy/etc/ssl/certs/haproxy/ as combined PEM files (certificate + private key). Each file should be named after the domain (e.g., example.com.pem).Validate configuration
Test the HAProxy configuration for syntax errors before starting the service.
sudo haproxy -f /etc/haproxy/haproxy.cfg -cEnable and start HAProxy
Enable HAProxy to start on boot and start the service.
sudo systemctl enable haproxy
sudo systemctl start haproxy
sudo systemctl status haproxyAdvanced ACL patterns and use cases
Geographic routing with GeoIP
Configure geographic-based routing using GeoIP databases for region-specific backends.
# GeoIP-based routing (requires GeoIP database)
acl is_eu_country src_geoip_country -f /etc/haproxy/eu_countries.txt
acl is_us_country src_geoip_country -f /etc/haproxy/us_countries.txt
acl is_asia_country src_geoip_country -f /etc/haproxy/asia_countries.txt
# Route to regional backends
use_backend eu_backend if is_eu_country
use_backend us_backend if is_us_country
use_backend asia_backend if is_asia_countryA/B testing with percentage-based routing
Implement A/B testing by routing a percentage of traffic to different backends.
# A/B testing - route 20% to version B
acl is_test_user rand(100) lt 20
acl has_ab_cookie hdr_sub(Cookie) ab_test=version_b
# Route test traffic
use_backend web_backend_v2 if is_test_user || has_ab_cookieCanary deployments with header-based routing
Route traffic to canary deployments based on special headers or user segments.
# Canary deployment routing
acl is_canary_user hdr(X-Canary-User) -i true
acl is_beta_tester hdr_sub(Cookie) beta_tester=true
acl is_internal_user hdr(X-Internal-User) -i true
# Route canary traffic
use_backend canary_backend if is_canary_user || is_beta_tester || is_internal_userDynamic map management
Hot-reload map files without downtime
Update map files and reload them without restarting HAProxy using the stats socket.
# Add new domain mapping
echo "newdomain.example.com web_backend" | sudo tee -a /etc/haproxy/maps/domains.map
# Reload the map file via stats socket
echo "clear map /etc/haproxy/maps/domains.map" | sudo socat stdio /run/haproxy/admin.sock
echo "show map /etc/haproxy/maps/domains.map" | sudo socat stdio /run/haproxy/admin.sockCreate map management script
Create a script to manage map files dynamically with validation and hot-reload.
#!/bin/bash
# HAProxy Map Manager Script
MAP_DIR="/etc/haproxy/maps"
SOCK_FILE="/run/haproxy/admin.sock"
function add_domain_mapping() {
local domain=$1
local backend=$2
local map_file="$MAP_DIR/domains.map"
if [ -z "$domain" ] || [ -z "$backend" ]; then
echo "Usage: add_domain_mapping Make script executable
Set proper permissions for the map management script.
sudo chmod +x /usr/local/bin/haproxy-map-manager.sh
sudo chown root:haproxy /usr/local/bin/haproxy-map-manager.shSSL SNI routing configuration
Advanced SNI routing with wildcard certificates
Configure advanced SNI routing with wildcard certificates and subdomain handling.
# Advanced SNI routing
frontend ssl_frontend
bind *:443 ssl crt /etc/ssl/certs/haproxy/ crt /etc/ssl/certs/haproxy/wildcard/
# SNIAutomated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default configuration
DEFAULT_DOMAIN="example.com"
DOMAIN="${1:-$DEFAULT_DOMAIN}"
# Usage message
usage() {
echo "Usage: $0 [domain]"
echo " domain: Primary domain for HAProxy configuration (default: $DEFAULT_DOMAIN)"
exit 1
}
# Logging functions
log_info() {
echo -e "${GREEN}$1${NC}"
}
log_warn() {
echo -e "${YELLOW}$1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Cleanup function for rollback
cleanup() {
log_warn "Error occurred. Cleaning up..."
systemctl stop haproxy 2>/dev/null || true
rm -rf /etc/haproxy/maps 2>/dev/null || true
}
trap cleanup ERR
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
HAPROXY_USER="haproxy"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
HAPROXY_USER="haproxy"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
HAPROXY_USER="haproxy"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "[1/8] Updating package repositories..."
$PKG_UPDATE
log_info "[2/8] Installing HAProxy..."
$PKG_INSTALL haproxy
log_info "[3/8] Creating HAProxy maps directory..."
mkdir -p /etc/haproxy/maps
chown $HAPROXY_USER:$HAPROXY_USER /etc/haproxy/maps
chmod 755 /etc/haproxy/maps
log_info "[4/8] Creating domain-to-backend map file..."
cat > /etc/haproxy/maps/domains.map << EOF
$DOMAIN web_backend
api.$DOMAIN api_backend
admin.$DOMAIN admin_backend
staging.$DOMAIN staging_backend
beta.$DOMAIN beta_backend
EOF
log_info "[5/8] Creating path-based routing map..."
cat > /etc/haproxy/maps/paths.map << EOF
^/api/ api_backend
^/admin/ admin_backend
^/static/ static_backend
^/upload/ upload_backend
^/webhooks/ webhook_backend
EOF
log_info "[6/8] Creating user agent routing map..."
cat > /etc/haproxy/maps/useragent.map << EOF
Mobile mobile_backend
Android mobile_backend
iPhone mobile_backend
iPad mobile_backend
Bot bot_backend
curl api_backend
EOF
log_info "[7/8] Creating maintenance mode map..."
cat > /etc/haproxy/maps/maintenance.map << EOF
$DOMAIN false
api.$DOMAIN false
admin.$DOMAIN false
staging.$DOMAIN true
beta.$DOMAIN false
EOF
# Set proper permissions for map files
chown $HAPROXY_USER:$HAPROXY_USER /etc/haproxy/maps/*
chmod 644 /etc/haproxy/maps/*
log_info "[8/8] Configuring HAProxy with advanced ACLs..."
cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
log stdout local0 info
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
ssl-default-bind-ciphers ECDHE+aRSA+AES256+GCM+SHA384:ECDHE+aRSA+CHACHA20:ECDHE+aRSA+AES128+GCM+SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
mode http
log global
option httplog
option dontlognull
option forwardfor
timeout connect 5000
timeout client 50000
timeout server 50000
frontend web_frontend
bind *:80
# ACL definitions using map files
acl domain_maintenance map(/etc/haproxy/maps/maintenance.map) true
acl is_api_path path_beg /api/
acl is_admin_path path_beg /admin/
acl is_static_path path_beg /static/
acl is_mobile_agent hdr_sub(User-Agent) -i Mobile Android iPhone iPad
acl is_bot_agent hdr_sub(User-Agent) -i bot crawler spider
# Routing decisions
use_backend maintenance_backend if domain_maintenance
use_backend api_backend if is_api_path
use_backend admin_backend if is_admin_path
use_backend static_backend if is_static_path
use_backend mobile_backend if is_mobile_agent
use_backend bot_backend if is_bot_agent
default_backend web_backend
backend web_backend
balance roundrobin
server web1 127.0.0.1:8080 check
server web2 127.0.0.1:8081 check
backend api_backend
balance roundrobin
server api1 127.0.0.1:8082 check
server api2 127.0.0.1:8083 check
backend admin_backend
balance roundrobin
server admin1 127.0.0.1:8084 check
backend static_backend
balance roundrobin
server static1 127.0.0.1:8085 check
backend mobile_backend
balance roundrobin
server mobile1 127.0.0.1:8086 check
backend bot_backend
balance roundrobin
server bot1 127.0.0.1:8087 check
backend maintenance_backend
http-request return status 503 content-type text/html string "Service temporarily unavailable"
listen stats
bind *:8404
stats enable
stats uri /
stats refresh 30s
stats admin if TRUE
EOF
# Validate HAProxy configuration
if ! haproxy -f /etc/haproxy/haproxy.cfg -c; then
log_error "HAProxy configuration validation failed"
exit 1
fi
# Enable and start HAProxy service
systemctl enable haproxy
systemctl restart haproxy
# Configure firewall based on distribution
if command -v ufw >/dev/null 2>&1; then
ufw allow 80/tcp
ufw allow 8404/tcp
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=8404/tcp
firewall-cmd --reload
fi
# Verification checks
log_info "Verifying installation..."
if systemctl is-active --quiet haproxy; then
log_info "✓ HAProxy service is running"
else
log_error "✗ HAProxy service failed to start"
exit 1
fi
if [ -d /etc/haproxy/maps ] && [ -f /etc/haproxy/maps/domains.map ]; then
log_info "✓ HAProxy maps directory and files created"
else
log_error "✗ HAProxy maps not properly created"
exit 1
fi
log_info "HAProxy advanced routing setup completed successfully!"
log_info "Configuration file: /etc/haproxy/haproxy.cfg"
log_info "Maps directory: /etc/haproxy/maps"
log_info "Stats interface: http://localhost:8404"
log_warn "Note: Update backend server addresses in /etc/haproxy/haproxy.cfg for your environment"
Review the script before running. Execute with: bash install.sh