HAProxy : load balancer pour répartir la charge

HAProxy : load balancer pour répartir la charge

Installez HAProxy pour répartir le trafic entre plusieurs serveurs backend, gérer les healthchecks et terminer le SSL. Outil de référence utilisé par Github, Twitter, Stack Overflow pour des millions de connexions.

Introduction

Lorsqu'un seul serveur ne suffit plus pour encaisser la charge, on passe au load balancing : un proxy frontal distribue les requêtes vers plusieurs backends. HAProxy est le load balancer de référence open-source.

Cas d'usage :

  • Site web → 3 serveurs Nginx en backend, basculement auto si l'un tombe
  • API → distribution round-robin avec sticky sessions
  • Base de données → load balancing read replicas MySQL/PostgreSQL
  • Terminaison SSL centralisée + healthchecks avancés

Prérequis

  • VPS Linux dédié au load balancer (Debian 12 / Ubuntu 22.04+)
  • Au moins 2 serveurs backend déjà en place
  • Un nom de domaine (optionnel mais recommandé)

Étape 1 : Installation

sudo apt update
sudo apt install -y haproxy
sudo systemctl enable --now haproxy
sudo systemctl status haproxy

Version recommandée : HAProxy 2.8+ (LTS).

Étape 2 : Architecture cible

                    ┌──────────────┐
   Clients ────────▶│   HAProxy    │
                    │ (VPS LB)     │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌─────────┐  ┌─────────┐  ┌─────────┐
        │ Web 1   │  │ Web 2   │  │ Web 3   │
        │ 10.0.0.10│ │10.0.0.11│  │10.0.0.12│
        └─────────┘  └─────────┘  └─────────┘

Les backends communiquent en privé (idéalement via un réseau privé entre VPS, ou via WireGuard).

Étape 3 : Config minimale (HTTP)

sudo nano /etc/haproxy/haproxy.cfg
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 moderne
    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
    log global
    mode http
    option httplog
    option dontlognull
    option http-keep-alive
    option forwardfor
    timeout connect 5s
    timeout client 60s
    timeout server 60s
    timeout http-request 10s
    timeout http-keep-alive 15s
    maxconn 50000

# Stats UI (interne)
listen stats
    bind 127.0.0.1:8404
    stats enable
    stats uri /
    stats refresh 10s
    stats auth admin:UnMotDePasseFort

# Frontend
frontend http_front
    bind *:80
    default_backend web_servers

# Backend
backend web_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    server web1 10.0.0.10:80 check inter 2000 rise 2 fall 3
    server web2 10.0.0.11:80 check inter 2000 rise 2 fall 3
    server web3 10.0.0.12:80 check inter 2000 rise 2 fall 3

Détails importants :

  • balance roundrobin : distribution équitable. Autres : leastconn (moins de connexions actives), source (sticky par IP)
  • option httpchk GET /health : healthcheck sur /health (votre app doit répondre 200)
  • check inter 2000 rise 2 fall 3 : check toutes les 2s, considéré up après 2 succès, down après 3 échecs

Validez la config :

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

Rechargez :

sudo systemctl reload haproxy

Étape 4 : Implémenter l'endpoint healthcheck côté backend

Sur chaque serveur backend, ajoutez un endpoint /health qui retourne 200 si l'app va bien :

Nginx

server {
    location = /health {
        access_log off;
        return 200 "OK";
        add_header Content-Type text/plain;
    }
}

Node.js

app.get('/health', (req, res) => {
    res.status(200).send('OK');
});

Plus avancé : vérifiez aussi MySQL et Redis dans le healthcheck pour ne pas router vers un backend dont la DB est down.

Étape 5 : Stats UI

Ouvrez http://127.0.0.1:8404 dans un tunnel SSH :

ssh -L 8404:127.0.0.1:8404 user@IP_DU_VPS_HAPROXY

Puis http://localhost:8404 dans votre navigateur. Identifiants : admin / UnMotDePasseFort.

Vous voyez :

  • État des backends (UP/DOWN, en vert/rouge)
  • Statistiques par serveur
  • Charges en temps réel

Étape 6 : Ajouter le SSL (terminaison)

HAProxy peut terminer le SSL et discuter en HTTP avec les backends (architecture courante).

Préparer le certificat

Avec Let's Encrypt :

sudo apt install -y certbot
sudo systemctl stop haproxy
sudo certbot certonly --standalone -d votre-domaine.com
sudo systemctl start haproxy

Combinez le cert + key dans un seul fichier pour HAProxy :

sudo bash -c "cat /etc/letsencrypt/live/votre-domaine.com/fullchain.pem /etc/letsencrypt/live/votre-domaine.com/privkey.pem > /etc/ssl/haproxy.pem"
sudo chmod 600 /etc/ssl/haproxy.pem

Modifier la config

frontend https_front
    bind *:80
    bind *:443 ssl crt /etc/ssl/haproxy.pem alpn h2,http/1.1
    
    # Force HTTPS
    http-request redirect scheme https unless { ssl_fc }
    
    # HSTS
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
    
    default_backend web_servers

Rechargez :

sudo systemctl reload haproxy

Étape 7 : Renouvellement auto SSL avec hook

Pour que Let's Encrypt renouvelle sans casser HAProxy :

sudo nano /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
#!/bin/bash
DOMAIN=votre-domaine.com
cat /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /etc/letsencrypt/live/${DOMAIN}/privkey.pem > /etc/ssl/haproxy.pem
chmod 600 /etc/ssl/haproxy.pem
systemctl reload haproxy
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh

Étape 8 : Algorithmes de load balancing

Selon votre cas :

backend web_servers
    # Round-robin (par défaut)
    balance roundrobin
    
    # Least connections
    balance leastconn
    
    # Source IP hashing (sticky session naturelle)
    balance source
    
    # URI hashing (cache-friendly)
    balance uri
    
    # Sticky session via cookie
    cookie SERVERID insert indirect nocache
    server web1 10.0.0.10:80 cookie web1 check
    server web2 10.0.0.11:80 cookie web2 check

Étape 9 : ACLs (routage avancé)

Routez par URL, header ou IP :

frontend https_front
    bind *:443 ssl crt /etc/ssl/haproxy.pem
    
    # Routage par path
    acl is_api path_beg /api
    acl is_admin path_beg /admin
    
    # Routage par hostname
    acl host_blog hdr(host) -i blog.votre-domaine.com
    
    # Routage par IP (limiter /admin)
    acl admin_whitelist src 82.26.157.10
    
    # Apply
    use_backend api_servers if is_api
    use_backend admin_servers if is_admin admin_whitelist
    use_backend blog_servers if host_blog
    default_backend web_servers

Étape 10 : Load balancing TCP (MySQL, Redis, SSH)

HAProxy peut aussi load-balancer des protocoles TCP (mode tcp) :

frontend mysql_front
    bind *:3306
    mode tcp
    default_backend mysql_servers

backend mysql_servers
    mode tcp
    balance leastconn
    option mysql-check user haproxy_check
    server db1 10.0.0.20:3306 check
    server db2 10.0.0.21:3306 check backup  # backup = utilisé si db1 down

Étape 11 : Limiter les connexions (anti-flood)

frontend https_front
    bind *:443 ssl crt /etc/ssl/haproxy.pem
    
    # Stick table (compteur par IP)
    stick-table type ipv6 size 100k expire 30s store conn_rate(10s),http_req_rate(10s)
    
    # Limites
    http-request track-sc0 src
    http-request reject if { sc_conn_rate(0) gt 50 }      # > 50 connexions/10s = blocage
    http-request reject if { sc_http_req_rate(0) gt 200 } # > 200 req/10s = blocage

Étape 12 : Logs et monitoring

# Logs en temps réel
sudo tail -f /var/log/haproxy.log

# Erreurs uniquement
sudo grep -i error /var/log/haproxy.log

# Top backends par requêtes
sudo awk '{print $7}' /var/log/haproxy.log | sort | uniq -c | sort -rn | head -10

Pour du monitoring continu : voir le tuto Prometheus + Grafana avec haproxy_exporter.

Dépannage

"no backend available"

Tous vos backends sont marqués DOWN. Vérifiez les healthchecks :

curl http://10.0.0.10/health

Doit retourner 200 OK.

"503 Service Unavailable"

Soit aucun backend disponible, soit timeout server trop court. Augmentez timeout server.

SSL handshake errors

Le fichier .pem est mal formé. Régénérez :

cat fullchain.pem privkey.pem > haproxy.pem

L'ordre compte : chain puis key.

Stats UI pas accessible

Vérifiez que le port 8404 n'est pas bloqué par UFW. Préférez tunneling SSH plutôt qu'exposer.

Commandes utiles

# Tester la config
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Recharger (zero downtime)
sudo systemctl reload haproxy

# Voir les sockets actifs
sudo socat /run/haproxy/admin.sock - <<< "show info"

# Désactiver un serveur (maintenance)
sudo socat /run/haproxy/admin.sock - <<< "disable server web_servers/web1"

# Réactiver
sudo socat /run/haproxy/admin.sock - <<< "enable server web_servers/web1"

# Voir l'état des backends
sudo socat /run/haproxy/admin.sock - <<< "show servers state"

# Reset des stats
sudo socat /run/haproxy/admin.sock - <<< "clear counters"

Conclusion

Avec HAProxy en frontal :

  • Votre infra tolère la panne d'un backend (failover auto)
  • Vous distribuez la charge entre N serveurs
  • Vous centralisez la terminaison SSL
  • Vous protégez contre le flood applicatif

Pour aller plus loin :

  • Mettez deux HAProxy en HA avec keepalived (failover de l'IP virtuelle)
  • Utilisez HAProxy Data Plane API pour pilotage automatisé
  • Migrez vers Traefik si vous voulez du service discovery Docker/Kubernetes natif

Ressources

Rejoignez notre serveur communautaire Discord

Pour toute question, suggestion ou simplement pour discuter avec la communauté, rejoignez-nous sur Discord !

900+Membres