Un balanceador solo aporta alta disponibilidad si deja de enviar tráfico a los servidores que fallan. HAProxy lo hace con comprobaciones de salud activas: consulta periódicamente cada backend y lo saca de la rotación cuando no responde como se espera, sin esperar a que falle una petición real. En este tutorial configurarás en Ubuntu 24.04 comprobaciones HTTP con umbrales de caída y recuperación, un servidor de respaldo, una página de mantenimiento cuando no queda ningún backend y el drenado manual de servidores para hacer mantenimiento sin cortar conexiones. Todo se prueba con un laboratorio de tres backends en la misma máquina.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • El puerto 80 libre.

En producción los backends estarán en otras máquinas, normalmente en una red privada; bastará con cambiar las direcciones de las líneas server. En el laboratorio son tres sitios de Nginx escuchando en 127.0.0.1.

Paso 1: Instalar HAProxy, Nginx y socat

Ubuntu 24.04 incluye HAProxy 2.8 LTS. Nginx servirá los backends de prueba y socat permite hablar con el socket de administración de HAProxy:

sudo apt update
sudo apt install haproxy nginx socat

Desactiva el sitio por defecto de Nginx, que ocupa el puerto 80 que usará HAProxy:

sudo rm /etc/nginx/sites-enabled/default
sudo systemctl reload nginx

Si usas UFW, permite HTTP:

sudo ufw allow 80/tcp

Paso 2: Crear backends con un endpoint de salud

Una buena comprobación de salud no se limita a ver si el puerto responde: consulta una ruta que la propia aplicación controla. Así la aplicación puede declararse no disponible (por ejemplo, si pierde la conexión con la base de datos) y también puedes sacar un servidor de servicio a propósito.

En el laboratorio, /health devuelve 200 mientras exista el archivo /var/www/backendN/health y 503 si se borra. Crea los archivos:

for i in 1 2 3; do
  sudo mkdir -p /var/www/backend$i
  echo OK | sudo tee /var/www/backend$i/health > /dev/null
done

Crea la configuración de los tres backends:

sudo nano /etc/nginx/conf.d/test-backends.conf
server {
    listen 127.0.0.1:8081;
    root /var/www/backend1;
    default_type text/plain;
    location = /health { try_files /health =503; }
    location / { return 200 "backend-1\n"; }
}

server {
    listen 127.0.0.1:8082;
    root /var/www/backend2;
    default_type text/plain;
    location = /health { try_files /health =503; }
    location / { return 200 "backend-2\n"; }
}

server {
    listen 127.0.0.1:8083;
    root /var/www/backend3;
    default_type text/plain;
    location = /health { try_files /health =503; }
    location / { return 200 "backend-3\n"; }
}

Valida, recarga y prueba el endpoint:

sudo nginx -t && sudo systemctl reload nginx
curl -i http://127.0.0.1:8081/health
HTTP/1.1 200 OK
Content-Type: text/plain

OK

Paso 3: Configurar HAProxy con comprobaciones HTTP

Primero crea la página que HAProxy devolverá cuando no quede ningún servidor disponible. Un errorfile es una respuesta HTTP completa, cabeceras incluidas:

sudo nano /etc/haproxy/errors/maintenance.http
HTTP/1.1 503 Service Unavailable
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
Connection: close

<!DOCTYPE html>
<html lang="es">
<head><meta charset="utf-8"><title>Mantenimiento</title></head>
<body>
  <h1>Estamos en mantenimiento</h1>
  <p>Vuelve a intentarlo en unos minutos.</p>
</body>
</html>

Guarda una copia de la configuración original de HAProxy y ábrela:

sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.orig
sudo nano /etc/haproxy/haproxy.cfg

Sustituye su contenido por:

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

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  redispatch
    retries 2
    timeout connect 3s
    timeout client  30s
    timeout server  30s

frontend web_in
    bind :80
    default_backend web_servers

backend web_servers
    balance roundrobin

    option httpchk
    http-check send meth GET uri /health ver HTTP/1.1 hdr Host localhost
    http-check expect status 200

    default-server inter 2s fall 3 rise 2 on-marked-down shutdown-sessions
    errorfile 503 /etc/haproxy/errors/maintenance.http

    server web1 127.0.0.1:8081 check
    server web2 127.0.0.1:8082 check
    server web3 127.0.0.1:8083 check backup

frontend stats
    bind 127.0.0.1:8404
    stats enable
    stats uri /stats
    stats refresh 5s

Qué controla cada parte:

DirectivaEfecto
option httpchk + http-check sendEnvía GET /health con cabecera Host a cada servidor. Sin httpchk, check solo comprueba que el puerto TCP acepta conexiones.
http-check expect status 200Solo un 200 cuenta como éxito. Sin esta línea, cualquier 2xx o 3xx vale.
inter 2sIntervalo entre comprobaciones.
fall 3Tres fallos seguidos marcan el servidor como DOWN (unos 6 segundos con inter 2s).
rise 2Dos éxitos seguidos lo devuelven a UP, para no meter en rotación un servidor que aún está arrancando.
on-marked-down shutdown-sessionsCierra las conexiones abiertas con un servidor cuando se marca como caído, en lugar de dejarlas colgadas.
backupweb3 solo recibe tráfico cuando no queda ningún servidor principal disponible.
option redispatch + retries 2Si falla la conexión con un servidor, la petición se reintenta en otro.
errorfile 503Página que se devuelve cuando el backend no tiene ningún servidor disponible.

default-server aplica esos parámetros a todas las líneas server del backend; puedes sobrescribirlos en una línea concreta.

Valida y recarga:

sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
Configuration file is valid

Comprueba que el tráfico se reparte entre los dos servidores principales:

for i in $(seq 1 4); do curl -s http://localhost/; done
backend-1
backend-2
backend-1
backend-2

backend-3 no aparece porque es de respaldo.

Paso 4: Ver el estado de los servidores

El socket de administración devuelve el estado de cada servidor. La salida de show stat es CSV; los campos 1, 2 y 18 son el backend, el servidor y su estado:

echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | cut -d, -f1,2,18 | grep web_servers
web_servers,web1,UP
web_servers,web2,UP
web_servers,web3,UP
web_servers,BACKEND,UP

Para ver la página de estadísticas en el navegador, abre un túnel SSH desde tu equipo con ssh -L 8404:127.0.0.1:8404 your_user@your_server_ip y visita http://localhost:8404/stats.

Deja abierta una segunda terminal siguiendo el log de HAProxy para ver los cambios de estado en los pasos siguientes:

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

Paso 5: Probar la conmutación por error

Simula que web1 deja de estar sano borrando su archivo de salud:

sudo rm /var/www/backend1/health

En unos 6 segundos (tres comprobaciones fallidas) aparece en el log:

Server web_servers/web1 is DOWN, reason: Layer7 wrong status, code: 503, info: "Service Temporarily Unavailable", check duration: 0ms. 1 active and 1 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.

Ahora todo el tráfico va a web2:

for i in $(seq 1 3); do curl -s http://localhost/; done
backend-2
backend-2
backend-2

Haz caer también web2. Al no quedar servidores principales, entra el de respaldo:

sudo rm /var/www/backend2/health
sleep 8
for i in $(seq 1 2); do curl -s http://localhost/; done
backend-3
backend-3

El log lo indica con 0 active and 1 backup servers left. Running on backup. Por último, haz caer web3:

sudo rm /var/www/backend3/health
sleep 8
curl -i http://localhost/
HTTP/1.1 503 Service Unavailable
content-type: text/html; charset=utf-8
cache-control: no-store
...
<h1>Estamos en mantenimiento</h1>

El log registra backend web_servers has no server available!. Los clientes ven tu página de mantenimiento en lugar de un error genérico.

Restaura los tres servidores:

for i in 1 2 3; do echo OK | sudo tee /var/www/backend$i/health > /dev/null; done

Tras dos comprobaciones correctas (unos 4 segundos) cada servidor vuelve a UP:

Server web_servers/web1 is UP, reason: Layer7 check passed, code: 200, check duration: 0ms. 1 active and 0 backup servers online. 0 sessions requeued, 0 total in queue.

Paso 6: Drenar un servidor para hacer mantenimiento

Antes de actualizar o reiniciar un backend, conviene sacarlo de rotación de forma ordenada. El estado drain deja de enviarle conexiones nuevas pero respeta las que ya tiene abiertas:

echo "set server web_servers/web1 state drain" | sudo socat stdio /run/haproxy/admin.sock
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | cut -d, -f1,2,18 | grep web_servers
web_servers,web1,DRAIN
web_servers,web2,UP
web_servers,web3,UP
web_servers,BACKEND,UP

Cuando termines el mantenimiento, devuélvelo a la rotación:

echo "set server web_servers/web1 state ready" | sudo socat stdio /run/haproxy/admin.sock

Otros estados útiles son maint, que lo saca por completo y detiene también sus comprobaciones, y set server web_servers/web1 weight 50% para reducir su carga temporalmente. Los cambios hechos por el socket solo viven en memoria: se pierden si reinicias o recargas HAProxy.

Paso 7: Ajustar la velocidad de detección

Con inter 2s fall 3 un servidor caído tarda unos 6 segundos en salir de la rotación. Para reaccionar antes sin multiplicar las comprobaciones normales, HAProxy permite intervalos distintos según el estado:

    default-server inter 3s fastinter 1s downinter 5s fall 3 rise 2 on-marked-down shutdown-sessions
  • fastinter 1s: intervalo usado mientras un servidor está cambiando de estado (tras el primer fallo o el primer éxito). La caída se detecta en unos 3 segundos.
  • downinter 5s: intervalo para servidores ya caídos, para no insistir innecesariamente.

Si además quieres validar el contenido de la respuesta y no solo el código, sustituye la línea http-check expect por http-check expect string OK, que exige que el cuerpo contenga ese texto.

Paso 8: Comprobaciones para servicios que no son HTTP

Para backends TCP (bases de datos, Redis, colas) usa mode tcp en el backend. HAProxy incluye comprobaciones que hablan el protocolo del servicio, más fiables que comprobar solo el puerto. Estos bloques son de referencia: adáptalos a tus direcciones y añádelos junto a un frontend en mode tcp.

Redis, enviando PING y esperando +PONG:

backend redis_servers
    mode tcp
    option tcp-check
    tcp-check send PING\r\n
    tcp-check expect string +PONG
    server redis1 10.0.0.31:6379 check inter 2s fall 3 rise 2

PostgreSQL y MySQL o MariaDB, con un usuario dedicado sin privilegios que debes crear en la base de datos:

backend pg_servers
    mode tcp
    option pgsql-check user haproxy_check
    server pg1 10.0.0.41:5432 check

backend mysql_servers
    mode tcp
    option mysql-check user haproxy_check post-41
    server db1 10.0.0.51:3306 check

Estas comprobaciones confirman que el servidor responde a nivel de protocolo, pero no si es el primario o una réplica. Para distinguirlo necesitas un endpoint HTTP auxiliar (por ejemplo, Patroni expone uno para PostgreSQL) y combinar check port <puerto> con option httpchk.

Solución de problemas

  • Todos los servidores en DOWN con Layer4 connection problem: HAProxy no llega al puerto. Comprueba la dirección y el puerto con curl -i http://<ip>:<puerto>/health desde el balanceador y revisa el cortafuegos del backend.
  • Layer7 wrong status, code: 404: la ruta de salud no existe o depende del dominio. Revisa uri y la cabecera Host de http-check send.
  • Layer7 timeout: el endpoint tarda más que el tiempo límite de la comprobación (por defecto el valor de inter, o timeout check si lo defines en defaults). Haz que /health sea rápido y no ejecute consultas pesadas.
  • HAProxy no arranca tras editar errorfile: el archivo debe empezar por la línea de estado HTTP y no superar el tamaño de un búfer (16 KB por defecto). Revisa sudo haproxy -c -f /etc/haproxy/haproxy.cfg.
  • No ves cambios de estado en /var/log/haproxy.log: comprueba que rsyslog está activo con systemctl status rsyslog; como alternativa, usa sudo journalctl -u haproxy -f.

Conclusión

HAProxy ya saca automáticamente de la rotación los servidores que fallan su comprobación de salud, usa un servidor de respaldo cuando caen los principales, muestra una página de mantenimiento si no queda ninguno y te permite drenar servidores para hacer mantenimiento sin cortes. Como siguientes pasos, añade terminación TLS al frontend, envía los cambios de estado a tu sistema de alertas y elimina el punto único de fallo del propio balanceador con un segundo nodo HAProxy y keepalived compartiendo una IP flotante.