--- title: Howto HAProxy category: web HA --- * Documentation : * Statut de cette page : prod / bullseye [HAProxy](http://www.haproxy.org/) est un puissant *load balancer* pour les protocoles TCP/HTTP/HTTPS. Il gère la répartition de charge et la tolérance de panne. Son principal auteur est [Willy Tarreau](http://1wt.eu/#wami), un développeur actif du noyau Linux. HAProxy est écrit en langage C, il est optimisé pour Linux, mais tourne également sous BSD. Des sites web importants l'utilisent comme Twitter, Github, Reddit, Airbnb, etc. ## Installation ~~~ # apt install haproxy $ /usr/sbin/haproxy -version HA-Proxy version 2.2.9-2+deb11u5 2023/04/10 - https://haproxy.org/ Status: long-term supported branch - will stop receiving fixes around Q2 2025. Known bugs: http://www.haproxy.org/bugs/bugs-2.2.9.html Running on: Linux 5.10.0-20-amd64 #1 SMP Debian 5.10.158-2 (2022-12-13) x86_64 ~~~ ## Configuration La configuration se passe dans le fichier `/etc/haproxy/haproxy.cfg` : ~~~ global log 127.0.0.1 local5 debug defaults mode http listen www bind *:80 balance roundrobin option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www.example.com server www00 192.0.2.1:80 maxconn 50 check inter 10s server www01 192.0.2.2:80 maxconn 50 check inter 10s ~~~ On note l'activation des logs en *debug* ce qui permet de voir **toutes** les requêtes. Attention, il faut donc que le démon `syslog` ait un paramétrage sur la facilité `local5` (ou autre selon votre configuration). Pour `rsyslog` cela se fait ainsi dans `rsyslog.conf` : ~~~ local5.* -/var/log/haproxy.log ~~~ ### Checker la configuration On vérifie qu'il n'y a pas d'erreur de syntaxes : ~~~ haproxy -c -V -f /etc/haproxy/haproxy.cfg ~~~ ### Exemples de configuration basique Voici quelques exemples inspirants : ~~~ # Empecher les requetes de type PROPFIND acl ACL1 method PROPFIND http-request deny if ACL1 # Envoi vers un backend en fonction du path use_backend BACKEND1 if { path_beg /blog } ~~~ ## Configuration avancée ### Exemple d'une configuration avec frontend/backend HTTP ~~~ global log /dev/log local5 log /dev/log local5 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 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 default-server port 80 maxconn 250 on-error fail-check slowstart 60s inter 1m fastinter 5s downinter 10s weight 100 listen stats bind *:8080 stats enable stats uri / stats show-legends stats show-node stats realm Auth\ required stats auth foo:bar stats admin if TRUE frontend myfront option forwardfor maxconn 800 bind 0.0.0.0:80 # Garde en mémoire et log l'en-tête de la requête capture request header Host len 32 default_backend myback backend myback balance roundrobin server web01 192.0.2.1:80 check observe layer4 weight 100 server web02 192.0.2.2:80 check observe layer4 weight 100 server web03 192.0.2.3:80 check observe layer4 weight 100 ~~~ La visualisation des statistiques peut aussi se faire via la console : ~~~ hatop -s /run/haproxy/admin.sock ~~~ ### SSL ~~~ global # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private # Default ciphers to use on SSL-enabled listening sockets. # For more information, see ciphers(1SSL). This list is from: # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS ssl-default-bind-options no-sslv3 no-tls-tickets ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS ssl-default-server-options no-sslv3 no-tls-tickets ~~~ Dans un frontend il faut ensuite faire un "binding" avec des arguments pour le SSL : ~~~ frontend fe_https bind 0.0.0.0:443 ssl crt /etc/ssl/haproxy/example_com.pem http-request set-header X-Forwarded-Proto https default_backend myback ~~~ Le fichier `example_com.pem` doit contenir le certificat ainsi que la clé privée et éventuellement des paramètres Diffie-Hellman (tout au format PEM). Il est possible d'indiquer plusieurs fois `crt /chemin/vers/fichier.pem` pour avoir plusieurs certificats possibles. HAProxy utilisera alors le mécanisme de SNI. Si on indique plutôt un dossier (par exemple `/etc/ssl/haproxy/`) tous les fichiers trouvés seront chargés par ordre alphabétique. Pour chaque fichier PEM trouvé, HAProxy cherchera un fichier `.ocsp` du même nom. Il peut être vide ou contenir une réponse OCSP valide (au format DER). Cela active le mécanisme de « OCSP stapling » Tous les détails de configuration pour l'attribut `crt` sont consultables sur Dans le cas où HAProxy gère plusieurs domaines dont certains seulement ont un certificat SSL, HAProxy enverra par défaut le certificat défini par la directive `crt`. S’il pointe sur un répertoire, un des certificats du répertoire (ça ne semble pas être le premier/dernier par ordre alphabétique) sera envoyé. Pour éviter ce comportement, on peut rajouter la directive `strict-sni`. Dans ce cas, si HAProxy ne trouve pas le certificat qui correspond au domaine demandé par le client, il retournera l'erreur SSL *SSL_ERROR_BAD_CERT_DOMAIN*. Il est aussi possible de désigner un fichier contenant la liste de tous les PEM : ~~~ frontend fe_https bind 0.0.0.0:443 ssl crt-list /etc/ssl/crt-list ~~~ Le fichier crt-list contient le chemin des fichiers PEM, ligne par ligne, comme ceci : ~~~ /chemin/vers/fichier1.pem /chemin/vers/fichier2.pem /chemin/vers/fichier3.pem ~~~ #### Terminaison SSL Si HAProxy doit faire la **terminaison SSL et dialoguer en clair** avec le backend, on se contente de transmettre la requête. ~~~ backend myback balance roundrobin server web01 192.0.2.1:80 check observe layer4 weight 100 server web02 192.0.2.2:80 check observe layer4 weight 100 ~~~ Si HAProxy doit faire la **terminaison SSL et maintenir une communication chiffrée** avec le backend, on doit le spécifier dans le backend (avec l'argument `ssl verify [none|optional|required]`, car le port 443 ne suffit pas à forcer le ssl). ~~~ backend myback balance roundrobin server web01 192.0.2.1:443 ssl verify none check observe layer4 weight 100 server web02 192.0.2.2:443 ssl verify none check observe layer4 weight 100 ~~~ La vérification du SSL sur le backend est à voir en fonction des besoins de sécurité. Attention, HAProxy ne supporte pas le SNI pour la communication avec le backend, du moins en version 1.5. L'erreur n'est pas explicite, il se contente d'indiquer une erreur de négociation SSL, cela peut se voir en détail avec wireshark par exemple. Il faut donc s'assurer que le backend délivre le bon certificat par défaut. ### Exemple avec plusieurs backends et du « sticky session » ~~~ global [...] defaults [...] frontend http-in bind *:8080 # On définit des ACL qui associe un Host: HTTP à un backend acl is_domain1 hdr_end(host) -i domain1.example.com acl is_domain2 hdr_end(host) -i domain2.example.com use_backend domain1 if is_domain1 use_backend domain2 if is_domain2 default_backend domain1 backend domain1 # Avec cette directive, HAProxy ajoute automatiquement un cookie SERVERID aux réponses HTTP, # et l'utilise pour sélectionner le bon serveur lors de la prochaine requête cookie SERVERID insert indirect balance roundrobin # Pour ce serveur, la valeur du cookie SERVERID sera "web01" (directive "cookie") server web01 192.0.2.1:80 cookie web01 check # Pour ce serveur, la valeur du cookie SERVERID sera "web02" server web02 192.0.2.2:80 cookie web02 check backend domain2 cookie SERVERID insert indirect balance roundrobin server web01 192.0.2.1:80 cookie web01 check server web02 192.0.2.2:80 cookie web02 check ~~~ ### Reproduire un ProxyPass Apache Avec Apache il est courant de faire un proxy qui modifie le chemin (_path_) : ~~~ ProxyPass / http://localhost:9999/path/to/app ProxyPassReverse / http://localhost:9999/path/to/app ~~~ Ainsi, une requete à `/foo/bar` sera transise au final à `/path/to/app/foo/bar`. Il est possible de reproduire le même comportement directement dans HAProxy : ~~~ backend be_http mode http http-request set-path /path/to/app%[path] acl header_location res.hdr(Location) -m found http-response replace-header Location (https?://%[req.hdr(Host)](:[0-9]+)?)?(/path/to/app)(.*) \1\4 if header_location server localhost 127.0.0.1:9999 ~~~ La partie `http-request set-path` permet de modifier le path au moment du traitement de la requête (équivalent à `ProxyPass` pour Apache). Le serveur amont n'ayant aucune information de l'URL intiale, s'il doit envoyer un en-tête de redirection calculé de manière relative à la requête, celui-ci ne sera pas correct. Il faut le modifier à la volée avant de renvoyer la réponse. La partie `http-response replace-header` va donc remplacer la valeur de l'en-tête `Location`. L'expression régulière ne sera satisfaite que si le domaine d'origine est utilisé (ou totalement absent), conservant ainsi la possibilité d'avoir des redirections intactes vers d'autres domaines. Détail des captures : 1. http ou https, suivi du _host_ et éventuellement un port (facultatif) 2. port (factultatif) 3. partie du _path_ à supprimer 4. reste du _path_ à garder L'utilisation d'un ACL (très rapide) permet de ne faire l'opération (plus lente) que si l'entête est présent. ### Rediriger le trafic vers le serveur web local C'est utile par exemple pour accéder aux graphes Munin du load-balancer. Il faut faire écouter le serveur web local sur le port 81 (avec les bonnes restrictions d'accès) en activant le Proxy Protocol ([exemple pour Nginx](/HowtoNginx#nginx-en-aval)), et y rediriger les requêtes : ~~~ frontend myfront (...) acl is_localhost hdr(host) -i . # ex : mylb mylp.mydomain.com acl is_wan_ip hdr(host) -m ip use_backend be_localhost if is_localhost || is_wan_ip (...) backend be_localhost server local_www 127.0.0.1:81 maxconn 10 send-proxy-v2 ~~~ ### Exemple en mode TCP ~~~ frontend fe_memcached bind 127.0.0.1:11211 mode tcp default_backend be_memcached backend be_memcached mode tcp option tcp-check default-server check on-marked-down shutdown-session on-marked-up shutdown-backup-session server nosql00 192.0.2.3:11211 server nosql01 192.0.2.4:11211 backup ~~~ ### Exemple pour MySQL Il existe 2 modes principaux pour un proxy MySQL : * le mode simple, qui effectue un test de connexion au serveur MySQL ; * le mode avancé, qui exécute des tests poussés (et personnalisés) pour valider le bon fonctionnement du serveur. #### Mode simple HAProxy fourni une option "mysql-check". Il va alors faire une connexion identifiée au serveur MySQL, puis la fermer et vérifier dans les infos renvoyées que tout semble correct. Ce mode ne nécessite pas d'outillage supplémentaire et nous le recommandons lorsqu'HAProxy agit seulement comme un proxy et pas comme un load-balancer ou pour de la tolérance de panne. ~~~ frontend fe_mysql bind 127.0.0.1:3306 mode tcp default_backend be_mysql backend be_mysql mode tcp option mysql-check user haproxy_check post-41 default-server on-marked-down shutdown-sessions on-marked-up shutdown-backup-sessions check server sql00 192.0.2.1:3306 server sql01 192.0.2.2:3306 backup ~~~ Il faut penser à créer l'utilisateur "haproxy_check" (sans mot de passe mais sans droits et restreint à une IP source) sur les serveurs ciblés ~~~{.sql} CREATE USER haproxy_check@; ~~~ L'utilisation de `default-server` permet de définir des caractéristiques communes à tous les serveurs du backend te simplifier la configuration (cf. [documentation](http://docs.haproxy.org/2.4/configuration.html#4.2-default-server)). L'utilisation de `on-marked-down shutdown-sessions` permet d'interrompre immédiatement les sessions en cours lorsqu'un hôte est considéré comme "DOWN" (cf. [documentation](http://docs.haproxy.org/2.4/configuration.html#5.2-on-marked-down)). Idem pour `on-marked-up shutdown-backup-session` qui va couper les sessions sur les serveurs "backup" lorsque le serveur "actif" change. #### Mode avancé ### Protection d'attaques HAProxy peut prendre des mesures face à des attaques de type [HTTP flood](https://en.wikipedia.org/wiki/HTTP_Flood). Pour cela, il utilise un mécanisme qui permet de suivre l'activité des visiteurs en stockant en mémoire le nombres de requêtes par client. Cela est possible grâce à l'utilisation des « [stick tables](https://www.haproxy.com/blog/introduction-to-haproxy-stick-tables/) ». Les « [stick tables](https://www.haproxy.com/blog/introduction-to-haproxy-stick-tables/) » fournissent un stockage clé-valeur pouvant être utilisé pour suivre divers compteurs associés à chaque client. Ces compteurs peuvent se baser sur tout ce qui se trouve dans la requête (adresse IP, UserAgent, URL, token…). Les valeurs comptabilisées sont le nombre de requêtes ainsi que le taux sur une période donnée. La première chose à faire est de définir une directive `stick-table` dans un `backend` ou un `frontend`. Il faut avoir à l'esprit que chaque backend/frontend ne peut contenir qu'une directive `stick-table`. On peut voir cela comme une vraie limitation, si l'on veut par exemple pouvoir suivre le nombre de requêtes par IP ainsi que que le nombre de requêtes par IP pour chaque URL. On peut vouloir également utiliser ces compteurs dans plusieurs backend/frontend simultanément. La bonne nouvelle est qu'il est possible de définir des frontend/backend dont la seule utilité est de contenir une directive `stick-table`. Il est alors possible ensuite d'utiliser ce compteur ailleurs via une directive `http-request` y faisant référence. Dans l'exemple suivant, on définit deux backends. Le premier sert à enregistrer le nombre de requête par IP « per_ip_rates ». Le second sert à suivre le nombre requête par IP pour chaque URL « per_ip_and_url_rates » : ~~~ backend per_ip_rates stick-table type ip size 1m expire 10m store http_req_rate(10s) backend per_ip_and_url_rates stick-table type binary len 8 size 1m expire 1h store http_req_rate(10s) ~~~ Pour les détails, dans le backend « per_ip_rates » on définit une directive `stick-table` de type IP qui peut contenir jusqu'à 1 million d'entrées, qui expire au bout de 10 minutes et qui comptabilise le nombre de requêtes sur les 10 dernières secondes. Dans le second backend « per_ip_and_url_rates », on définit une directive `stick-table` de type binary (longueur de 8 bits), qui peut contenir jusqu'à 1 million d'entrées, qui expire au bout d'une heure et qui comptabilise le nombre de requêtes IP/URL sur les 10 dernières secondes. On peut ensuite utiliser ces deux compteurs dans les frontends/backend de notre choix en y faisant référence dans une directive `http-request` via le paramètre `table`. Dans l'exemple suivant, on crée deux blocs de 2 directives `http-request`. ~~~ frontend default […] http-request track-sc0 src table per_ip_rates unless { path_end .css .js .png .jpeg .gif } http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 } http-request track-sc1 url32+src table per_ip_and_url_rates unless { path_end .css .js .png .jpeg .gif } http-request deny deny_status 429 if { sc_http_req_rate(1) gt 10 } […] ~~~ Le premier bloc contient deux lignes dont la première définit une règle « track-sc0 » qui se basant sur la table du backend « per_ip_rates » en omettant toutes les requêtes vers certains types de fichiers (CSS, JS…). La seconde ligne indique ensuite de renvoyer un status 429 si le nombre des requêtes HTTP/sec pour une même IP selon les données provenant de « track-sc0 » est supérieur à 100 (donc 100 requêtes depuis une même IP sur les 10 dernières seconndes). Le second bloc contient quant à lui deux lignes dont la première définit une règle « track-sc1 » qui se base sur la table du backend « per_ip_and_url_rates » en omettant toutes les requêtes vers certains types de fichiers (CSS, JS…). La seconde ligne indique ensuite de renvoyer un status 429 si le nombre des requêtes HTTP/sec vers une URL unique depuis une même IP selon les données provenant de « track-sc1 » est supérieur à 10 (donc 10 requêtes depuis une même IP et vers la même URL sur les 10 dernières secondes). ### Protocol PROXY L'utilisation du protocol PROXY permet de faciliter la communication et la traçabilité lorsque des proxys sont impliqués. Voir la [présentation générale du protocol](https://www.haproxy.com/blog/use-the-proxy-protocol-to-preserve-a-clients-ip-address/). #### HAProxy en amont Pour envoyer du trafic à un serveur de backend en utilisant le protocol PROXY, il suffit d'ajouter le mot clé `send-proxy-v2` dans la définition du serveur : ~~~ backend be_web server web01 192.0.2.1:80 […] send-proxy-v2 ~~~ #### HAProxy en aval Pour recevoir du trafic en utilisant le protocol PROXY, il suffit d'ajouter le mot clé `accept-proxy` dans la définition du binding : ~~~ frontend foo bind […] accept-proxy ~~~ ## Check HTTP Cela consiste à utiliser un check http pour déterminer l'état du serveur. ~~~ frontend fe_mysql bind 127.0.0.1:3306 mode tcp default_backend be_mysql backend be_mysql mode tcp option httpchk HEAD http-check disable-on-404 default-server on-marked-down shutdown-sessions on-marked-up shutdown-backup-sessions check port 8306 server sql00 192.0.2.1:3306 server sql01 192.0.2.2:3306 backup ~~~ On note l'option **httpchk** qui va permettre de faire un check en HTTP et vérifier des conditions avancées (réplication OK, etc.). Un moyen simple (inspiré de ce [vieux blog post](http://sysbible.org/2008/12/04/having-haproxy-check-mysql-status-through-a-xinetd-script/)) est de créer un script qui sera déclenché par **xinetd**. ~~~ # apt install xinetd ~~~ On ajoute un service à xinetd, dans `/etc/xinetd.d/mysqlchk` (droits: root:root 0644) : ~~~ service mysqlchk { flags = REUSE socket_type = stream port = 8306 wait = no user = root server = /root/mysqlchk log_on_failure += USERID disable = no only_from = 192.0.2.0/27 10.0.0.1/24 per_source = UNLIMITED } ~~~ Il faut penser à ajuster la liste d'adresses IP autorisées dans `only_from`. On peut utiliser IP, des plages… séparées par des espaces. On ajoute la ligne suivante dans `/etc/services` : ~~~ mysqlchk 8306/tcp # mysqlchk ~~~ On crée le script à exécuter dans `/root/mysqlchk` (droits: root:root 0750) : ~~~{.bash} #!/bin/sh # Mysql is down, return a 503 return="503 Service Unavailable" # Mysql is fine, return a 200 /usr/lib/nagios/plugins/check_mysql -f /etc/mysql/debian.cnf >/dev/null && return="200 OK" # Mysql is up but replication is not ok, return a 404 # You may want to comment this line in master/master mode # It disable server (NOLB status) when replication is down or lagging /usr/lib/nagios/plugins/check_mysql -f /etc/mysql/debian.cnf --check-slave -c 60 >/dev/null || return="404 Not Found" cat < * * [Haute performance sous Linux](HowtoDebian/Reseau#haute-performance) ### Optimisations TCP/kernel ~~~{.ini} net.netfilter.nf_conntrack_max=1000000 net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_fin_timeout=20 net.ipv4.ip_local_port_range=1025 65534 net.ipv4.tcp_max_orphans=262144 net.core.somaxconn=100000 net.core.netdev_max_backlog=100000 fs.file-max=2000000 ~~~ ### Optimisations systemd ~~~ $ cat /etc/systemd/system/haproxy.service.d/limits.conf [Service] LimitNOFILE=500000 ~~~ ### Optimisations haproxy.cfg ~~~ global maxconn 500000 #nbproc 2 # cpu-map 1 0 # cpu-map 2 1 tune.maxrewrite 8000 tune.http.maxhdr 800 tune.bufsize 60000 tune.ssl.cachesize 1000000 defaults maxconn 500000 ~~~ > *Note* : à partir d'HAProxy 2.5, les « optimisations » concernant le CPU ne sont plus nécessaires #### maxconn global vs defaults Attention, le `maxconn` (global) fixe une limite globale à HAProxy. Il n'est pas surchargé par le `maxconn` (defaults) qui va s'appliquer à tous les *frontend* et pourra lui être surchargé au cas par cas. ## Utiliser l'API runtime HaProxy propose une API accessible via un socket ou un port permettant de lui envoyer dynamiquement des commandes. **Attention, les changements dynamiques de configuration ne seront pas reportés dans la configuration statique.** Documentation : Les exemples d'utilisation suivants peuvent aussi être faits depuis `hatop` à l'onglet `5-CLI`. Par exemple, au lieu d'entrer `echo "show info" | socat stdio /run/haproxy/admin.sock`, on peut entrer `show info` dans le _shell_ de `hatop`. ### Debug ~~~ # echo "show info" | socat stdio /run/haproxy/admin.sock # echo "show acl" | socat stdio /run/haproxy/admin.sock # echo "show acl #" | socat stdio /run/haproxy/admin.sock ~~~ > Note : on peut aussi utiliser `nc(1)` du paquet `netcat-openbsd` : > > ~~~ > # echo "show info" | nc -U /run/haproxy/admin.sock > ~~~ ### Désactiver/Activer un serveur ~~~ # echo disable server / | socat stdio /run/haproxy/admin.sock # echo enable server / | socat stdio /run/haproxy/admin.sock ~~~ ### Afficher le status des FRONTEND/BACKEND ~~~ # echo "show stat" | socat stdio unix-connect:/run/haproxy/admin.sock | cut -d ',' -f1,2,18 ~~~ ### Afficher les certificats SSL utilisés A partir de HaProxy 2.2 : ~~~ # echo "show ssl cert" | socat stdio unix-connect:/run/haproxy/admin.sock # ou autre chemin de socket configuré dans "stats socket" ~~~ ## Lecture des logs HAProxy a des logs différents selon le type de connexion. Pour les connexions TCP ([Voir la doc complète](http://docs.haproxy.org/2.0/configuration.html#8.2.2)) : ~~~ frontend fnt mode tcp option tcplog log global default_backend bck backend bck server srv1 127.0.0.1:8000 >>> Feb 6 12:12:56 localhost haproxy[14387]: 10.0.1.2:33313 [06/Feb/2009:12:12:51.443] fnt bck/srv1 0/0/5007 212 -- 0/0/0/0/3 0/0 Field Format Extract from the example above 1 process_name '[' pid ']:' haproxy[14387]: 2 client_ip ':' client_port 10.0.1.2:33313 3 '[' accept_date ']' [06/Feb/2009:12:12:51.443] 4 frontend_name fnt 5 backend_name '/' server_name bck/srv1 6 Tw '/' Tc '/' Tt* 0/0/5007 7 bytes_read* 212 8 termination_state -- 9 actconn '/' feconn '/' beconn '/' srv_conn '/' retries* 0/0/0/0/3 10 srv_queue '/' backend_queue 0/0 ~~~ Pour les connexions HTTP ([Voir la doc complète](http://docs.haproxy.org/2.0/configuration.html#8.2.3)) :. ~~~ frontend http-in mode http option httplog log global default_backend bck backend static server srv1 127.0.0.1:8000 >>> Feb 6 12:14:14 localhost haproxy[14389]: 10.0.1.2:33317 [06/Feb/2009:12:14:14.655] http-in static/srv1 10/0/30/69/109 200 2750 - - ---- 1/1/1/1/0 0/0 {1wt.eu} {} "GET /index.html HTTP/1.1" Field Format Extract from the example above 1 process_name '[' pid ']:' haproxy[14389]: 2 client_ip ':' client_port 10.0.1.2:33317 3 '[' request_date ']' [06/Feb/2009:12:14:14.655] 4 frontend_name http-in 5 backend_name '/' server_name static/srv1 6 TR '/' Tw '/' Tc '/' Tr '/' Ta* 10/0/30/69/109 7 status_code 200 8 bytes_read* 2750 9 captured_request_cookie - 10 captured_response_cookie - 11 termination_state ---- 12 actconn '/' feconn '/' beconn '/' srv_conn '/' retries* 1/1/1/1/0 13 srv_queue '/' backend_queue 0/0 14 '{' captured_request_headers* '}' {haproxy.1wt.eu} 15 '{' captured_response_headers* '}' {} 16 '"' http_request '"' "GET /index.html HTTP/1.1" ~~~ ## Challenge ACME Si vous avez besoin de valider des challenges ACME (pour la création/renouvellement de certificats Let's Encrypt, par exemple), il est possible que le proxy fasse relai. Il faut d'abord que le frontend qui écoute sur le port de challenge (`80` pour `http-01`) détecte les requêtes du challenge et utilise alors un backend dédié. Si votre challenge est fait localement ou sur un autre serveur, il faudra ajuster l'adresse du serveur web faisant réellement le challenge. ~~~ frontend fe_www bind :80 bind :443 ssl crt /etc/ssl/haproxy/ alpn h2,http/1.1 option forwardfor acl letsencrypt path_dir -i /.well-known/letsencrypt use_backend be_letsencrypt if letsencrypt default_backend be_www backend be_letsencrypt server localhost 192.168.2.1:80 maxconn 10 ~~~ Le processus qui générera ensuite le certificat devra générer un fichier pour HAProxy, contenant la concaténation du certificat, l'éventuelle chaîne intermédiaire, la clé privée, les eventuels paramètres Diffie-Helmann et l'éventuelle réponse OCSP. ## Attaques (D)DOS ### Fail2Ban [Fail2Ban](HowtoFail2Ban) est indépendant de HAProxy, mais peut être utilisé pour bloquer des requêtes en amont pour qu'elles n'atteignent pas HAProxy. Cela nous a été utile dans certains cas d'attaques DDOS avec plusieurs centaines de milliers de requêtes (`maxconn 500000` atteint) où HAProxy devenait lent à traiter les requêtes. Exemple d'une règle qui bannit en fonction d'un entête `Host:` spécifique : ~~~ [haproxy-bad-host] enabled = true port = http,https logpath = /var/log/haproxy.log banaction = iptables-multiport maxretry = 1 bantime = 86400 findtime = 60 failregex = ^%(__prefix_line)s.* .* \{www\.example\.com/bad\} ~~~ ## HAPEE – HAProxy Enterprise Edition HAProxy est également disponible en édition « Enterprise », avec des fonctionnalités supplémentaires, un support direct et des outils additionnels. Plusieurs versions sont disponibles en parallèle. Liens utiles : * site commercial : * Customer Portal : * Documentation générale (publique) : * Documentation complète (privée) : ### Installation L'accès aux paquets Debian spécifiques se fait grâce à un dépôt APT dont l'URL contient une clé de license. L'installation se fait dans `/opt`, il ets donc important d'avoir quelques dizaines/centaines de Mo disponibles sur la partition associée. Les binaires chemins et outils changent par rapport à HAProxy. Exemples avec la version 2.4 (LTS actuelle) : * config générale : `/etc/hapee-2.6/hapee-lb.cfg` * unité systemd : `systemctl status hapee-2.6-lb.service` * binaire principal : `/opt/hapee-2.6/sbin/hapee-lb` * vérification de config : `/opt/hapee-2.6/sbin/hapee-lb -c -V -f /etc/hapee-2.6/hapee-lb.cfg` ### Outils complémentaires Des outils complémentaires sont proposés : * un paquet pour keepalived/vrrp * un module WAF à base de ModSecurity * une interface de monitoring plus riche * … ### Logs Par défaut, HAPEE écrit ses logs dans `/var/log/hapee-2.6/` avec un fichier par jour (grace à une configuration de rsyslog), et aucune configuration de logrotate n'est présente. Nous préférons personnaliser cela pour coller à la manière dont c'est géré dans le paquet Debian de HAProxy. On log dans un fichier non daté : ~~~{.diff} diff --git a/rsyslog.d/hapee-26-lb.conf b/rsyslog.d/hapee-26-lb.conf index 578fbcd..6c0dd48 100644 --- a/rsyslog.d/hapee-26-lb.conf +++ b/rsyslog.d/hapee-26-lb.conf @@ -7,8 +7,8 @@ $UDPServerRun 514 $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat # Configure template name for daily rotation -$template lb-access,"/var/log/hapee-2.6/lb-access-%$YEAR%%$MONTH%%$DAY%.log" -$template lb-admin,"/var/log/hapee-2.6/lb-admin-%$YEAR%%$MONTH%%$DAY%.log" +$template lb-access,"/var/log/hapee-2.6/lb-access.log" +$template lb-admin,"/var/log/hapee-2.6/lb-admin.log" # By default, hapee-lb sends trafic log with local0 facility and admin # logs on local1 ~~~ On ajoute une configuration logrotate dans `/etc/logrotate.d/hapee-26-lb` : ~~~ /var/log/hapee-2.6/*.log { daily rotate 52 missingok notifempty compress nodelaycompress postrotate /usr/lib/rsyslog/rsyslog-rotate endscript dateext } ~~~ ## FAQ ### Check HTTP et Erreur 400 Bad Request HAProxy 2.2 a modifié sonc omportement pour les checks HTTP (`option httpchk` dans un backend) et envoie désormais un en-tête `Content-Length: 0`. Malheureusement certains serveurs (comme les ELB de AWS) n'aiment pas ça et renvoient une erreur `400 Bad Request`. D'après [ce fil du forum d'HAProxy](https://discourse.haproxy.org/t/bad-request-on-health-check-due-to-content-length-header/6588) on peut contourner en faisant du HTTP dans un check TCP : ~~~ option tcp-check tcp-check comment send\ HTTP\ request tcp-check send GET\ /status\ HTTP/1.0\r\n tcp-check send User-Agent:\ Haproxy\ Health\ Check\r\n tcp-check send \r\n tcp-check expect rstring HTTP/1\..\ 200 comment check\ HTTP\ response ~~~ ### HTTP 100 CONTINUE Dans le cas de l'utilisation spécifique de `HTTP 100 CONTINUE`, cf [HowtoHTTP#http1.1-100-continue]() nous avons constaté qu'avec HAProxy 1.8, il ne retransmet pas la réponse `HTTP/1.1 100 Continue` au client [comme signalé ici](https://github.com/haproxy/haproxy/issues/1321) provoquant un timeout et une erreur 502 renvoyée par HAProxy (debug complexe par manque de log). Solution pour contourner : passer en version supérieure (par exemple HAProxy 2.4) ### Go away bad bots Ajout d'une règle : ~~~ # goaway bad bots acl goaway-badbots hdr_sub(user-agent) -i -f /etc/haproxy/blacklist-useragent.txt http-request deny if goaway-badbots ~~~ avec `/etc/haproxy/blacklist-useragent.txt` contenant : ~~~ thesis-research-bot Amazonbot Bytespider SeekportBot PetalBot DotBot YandexBot SEOkicks serpstatbot AhrefsBot MJ12bot ~~~