HAProxy es un balanceador de carga y proxy inverso de alto rendimiento para TCP y HTTP. Reparte las peticiones entre varios servidores, deja de enviar tráfico a los que fallan y puede terminar TLS para que los servidores de aplicación no tengan que hacerlo. En esta guía instalarás HAProxy en Ubuntu 24.04, lo pondrás delante de dos servidores web con comprobaciones de salud, añadirás HTTPS con Let's Encrypt, limitarás las peticiones por IP y activarás la página de estadísticas.

Requisitos previos

Necesitas:

  • Un servidor con Ubuntu 24.04 LTS para HAProxy, por ejemplo un VPS de CubePath, con un usuario no root con privilegios sudo.
  • Dos servidores web (Nginx, Apache o tu aplicación) que respondan por HTTP en el puerto 80 en una red privada. En esta guía se usan 10.0.0.11 y 10.0.0.12.
  • Un dominio con un registro DNS A que apunte a la IP pública del balanceador. Se usará your_domain como marcador.
  • Los puertos 80 y 443 accesibles desde Internet en el balanceador.

Paso 1: Instalar HAProxy

Ubuntu 24.04 incluye HAProxy 2.8, una versión LTS con soporte a largo plazo, suficiente para todo lo que se ve en esta guía:

sudo apt update
sudo apt install haproxy

Comprueba la versión y que el servicio está activo:

haproxy -v | head -n 1
systemctl status haproxy --no-pager
HAProxy version 2.8.x-1ubuntu3 - https://haproxy.org/
...
     Active: active (running)

Abre los puertos web en el cortafuegos:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Paso 2: Preparar los servidores web

Cada servidor web debe responder a una ruta de comprobación de salud. Lo más sencillo es un archivo estático /health. En cada servidor web con Nginx:

echo "ok" | sudo tee /var/www/html/health
hostname | sudo tee /var/www/html/index.html

La segunda línea hace que cada servidor devuelva su nombre, lo que te permitirá ver el reparto de carga más adelante. Desde el balanceador, confirma que llegas a ambos:

curl http://10.0.0.11/health
curl http://10.0.0.12/health
ok
ok

Paso 3: Configurar el balanceo HTTP

El archivo de configuración es /etc/haproxy/haproxy.cfg. Guarda una copia del original y ábrelo:

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

Deja la sección global que trae Ubuntu (define el usuario, el chroot, el socket de administración en /run/haproxy/admin.sock y los parámetros TLS por defecto) y sustituye todo lo que va desde defaults hasta el final por lo siguiente:

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  forwardfor
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    timeout http-request 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_in
    bind :80
    acl acme path_beg /.well-known/acme-challenge/
    use_backend letsencrypt if acme
    default_backend web_servers

backend web_servers
    balance roundrobin
    option httpchk
    http-check send meth GET uri /health
    http-check expect status 200
    default-server inter 3s fall 3 rise 2
    server web1 10.0.0.11:80 check
    server web2 10.0.0.12:80 check

backend letsencrypt
    server certbot 127.0.0.1:8888

Qué hace cada parte:

  • option forwardfor añade la cabecera X-Forwarded-For con la IP real del cliente.
  • http-check pide /health a cada servidor cada 3 segundos (inter 3s). Tras 3 fallos seguidos (fall 3) el servidor sale del reparto y vuelve tras 2 respuestas correctas (rise 2).
  • balance roundrobin reparte las peticiones por turnos. Para conexiones largas (WebSockets, bases de datos) suele ir mejor leastconn, y si necesitas que un cliente vaya siempre al mismo servidor sin cookies, source.
  • El backend letsencrypt servirá para obtener el certificado en el siguiente paso. De momento no hay nada escuchando en el puerto 8888, lo cual es normal.

Valida la sintaxis antes de aplicar cualquier cambio:

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

Recarga HAProxy. reload aplica la nueva configuración sin cortar las conexiones abiertas:

sudo systemctl reload haproxy

Haz varias peticiones seguidas y verás cómo se alternan los servidores (cada uno responde con su nombre de host, aquí web1 y web2):

for i in 1 2 3 4; do curl -s http://your_domain; done
web1
web2
web1
web2

Paso 4: Obtener un certificado de Let's Encrypt

HAProxy ya ocupa el puerto 80, así que Certbot se ejecutará en modo standalone escuchando en el puerto 8888, y HAProxy le reenviará las peticiones de validación gracias a la ACL acme del paso anterior. Instala Certbot:

sudo apt install certbot

Solicita el certificado, sustituyendo your_domain y tu correo:

sudo certbot certonly --standalone --http-01-port 8888 \
    -d your_domain --agree-tos -m [email protected] --no-eff-email
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/your_domain/privkey.pem

HAProxy espera la cadena de certificados y la clave privada en un único archivo PEM. Crea el directorio y combina ambos:

sudo mkdir -p /etc/haproxy/certs
sudo sh -c 'cat /etc/letsencrypt/live/your_domain/fullchain.pem /etc/letsencrypt/live/your_domain/privkey.pem > /etc/haproxy/certs/your_domain.pem'
sudo chmod 600 /etc/haproxy/certs/your_domain.pem

Para que el archivo combinado se regenere en cada renovación, crea un deploy hook de Certbot:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
#!/usr/bin/env bash
set -euo pipefail

domain="$(basename "$RENEWED_LINEAGE")"
cat "$RENEWED_LINEAGE/fullchain.pem" "$RENEWED_LINEAGE/privkey.pem" > "/etc/haproxy/certs/${domain}.pem"
chmod 600 "/etc/haproxy/certs/${domain}.pem"
systemctl reload haproxy

Hazlo ejecutable y prueba la renovación en modo simulación:

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:

El paquete de Certbot instala un temporizador de systemd (certbot.timer) que intenta la renovación dos veces al día.

Paso 5: Activar HTTPS y redirigir HTTP

Edita de nuevo /etc/haproxy/haproxy.cfg:

sudo nano /etc/haproxy/haproxy.cfg

Sustituye el frontend http_in por esta versión, que sigue atendiendo las validaciones de Let's Encrypt pero redirige el resto a HTTPS, y añade el nuevo frontend https_in:

frontend http_in
    bind :80
    acl acme path_beg /.well-known/acme-challenge/
    use_backend letsencrypt if acme
    http-request redirect scheme https code 301 unless acme

frontend https_in
    bind :443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
    http-request set-header X-Forwarded-Proto https
    http-response set-header Strict-Transport-Security "max-age=31536000"
    default_backend web_servers

crt /etc/haproxy/certs/ carga todos los PEM de ese directorio y elige el correcto según el nombre solicitado (SNI), así que podrás añadir más dominios sin tocar la configuración. alpn h2,http/1.1 habilita HTTP/2 con los clientes. Los servidores web siguen recibiendo HTTP en claro por la red privada, con la cabecera X-Forwarded-Proto para que la aplicación sepa que el cliente usó HTTPS.

Valida y recarga:

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

Comprueba la redirección y la respuesta por HTTPS:

curl -sI http://your_domain | head -n 2
curl -s https://your_domain
HTTP/1.1 301 Moved Permanently
content-length: 0
web1

Paso 6: Limitar las peticiones por IP

Una stick table permite a HAProxy llevar la cuenta de peticiones por IP y rechazar a quien supere un umbral. Añade estas líneas al frontend https_in, justo antes de default_backend:

    stick-table type ip size 100k expire 30s store http_req_rate(10s)
    http-request track-sc0 src
    http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }

Con esto, una IP que haga más de 100 peticiones en 10 segundos recibirá un 429 Too Many Requests hasta que baje de ese ritmo. Ajusta el umbral a tu tráfico real: una página con muchos recursos puede generar decenas de peticiones en una sola visita. Valida, recarga y, si tienes instalado ab (paquete apache2-utils), compruébalo desde otro equipo:

ab -n 300 -c 20 https://your_domain/

En el resultado, las respuestas rechazadas aparecen en Non-2xx responses.

Paso 7: Activar la página de estadísticas

HAProxy incluye una página web con el estado de cada frontend, backend y servidor. Añade al final de /etc/haproxy/haproxy.cfg un frontend dedicado, con una contraseña propia en lugar de your_strong_password:

frontend stats
    bind :8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:your_strong_password

Valida, recarga y permite el acceso al puerto 8404 solo desde tu IP de administración:

sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
sudo ufw allow from your_admin_ip to any port 8404 proto tcp

Abre http://your_domain:8404/stats en el navegador. Los servidores sanos aparecen en verde con el estado UP.

También puedes consultar y controlar HAProxy desde la terminal a través de su socket de administración con socat:

sudo apt install socat
echo "show servers state web_servers" | sudo socat stdio /run/haproxy/admin.sock

Para sacar un servidor del reparto antes de un mantenimiento sin cortar las conexiones activas, ponlo en modo drain y devuélvelo con ready cuando termines:

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

Estos cambios se pierden al reiniciar HAProxy; son para operaciones puntuales.

Paso 8: Revisar los registros

En Ubuntu, HAProxy envía sus logs a través de rsyslog al archivo /var/log/haproxy.log. Cada línea incluye el cliente, el frontend, el backend y el servidor que atendió la petición, los tiempos y el código de respuesta:

sudo tail -f /var/log/haproxy.log
... https_in~ web_servers/web2 0/0/1/2/3 200 312 - - ---- 3/3/0/0/0 0/0 "GET / HTTP/2.0"

Los cinco números separados por barras son, en milisegundos, el tiempo de recepción de la petición, la espera en cola, la conexión con el servidor, la respuesta del servidor y el total. Si el cuarto valor es alto, el cuello de botella está en tu aplicación, no en HAProxy.

Solución de problemas

Un servidor aparece como DOWN. Reproduce la comprobación de salud desde el balanceador con curl -i http://10.0.0.11/health. Si no devuelve 200, el problema está en el servidor web o en la ruta. Los cambios de estado quedan registrados en /var/log/haproxy.log con el motivo (Layer4 connection problem, Layer7 wrong status).

HAProxy no arranca tras un cambio. Ejecuta siempre sudo haproxy -c -f /etc/haproxy/haproxy.cfg antes de recargar; indica la línea exacta del error. Consulta también sudo journalctl -u haproxy -n 50.

Error unable to load SSL certificate. El archivo PEM no existe, está vacío o le falta la clave privada. Comprueba que contiene ambos bloques con sudo grep -c BEGIN /etc/haproxy/certs/your_domain.pem (debe devolver al menos 2).

La renovación de Certbot falla. Comprueba que la ACL acme sigue en el frontend del puerto 80 y que la redirección a HTTPS lleva unless acme.

Error 503 No server is available. Todos los servidores del backend están caídos. Revisa la página de estadísticas y los logs de los servidores web.

Conclusión

Tienes HAProxy repartiendo tráfico entre dos servidores web, retirando automáticamente los que fallan, terminando HTTPS con renovación automática del certificado y limitando el abuso por IP. Para eliminar el balanceador como punto único de fallo, el siguiente paso natural es montar un segundo nodo de HAProxy con Keepalived y una IP virtual. También puedes afinar los parámetros TCP del kernel con sysctl si esperas un volumen alto de conexiones.