HAProxy incluye una caché HTTP en memoria que guarda las respuestas de los backends y las sirve directamente, sin herramientas adicionales como Varnish. Está pensada como una caché pequeña y rápida (a veces llamada favicon cache) para recursos que se piden mucho y cambian poco. En este tutorial activarás la caché en HAProxy sobre Ubuntu 24.04, ajustarás el tiempo de vida de las entradas, excluirás las rutas que no deben cachearse y verificarás los aciertos.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Una aplicación HTTP escuchando en
127.0.0.1:8080. Si no tienes una, el paso 2 levanta un backend de prueba. - Conocimientos básicos de la estructura de
haproxy.cfg(frontend,backend).
Paso 1: Instalar HAProxy
Ubuntu 24.04 incluye HAProxy 2.8, una versión LTS con la caché completa. Instálalo junto con socat, que usarás para consultar el estado de la caché:
sudo apt update
sudo apt install haproxy socat
haproxy -v | head -n 1
HAProxy version 2.8.x 2024/xx/xx - https://haproxy.org/
Notasi necesitas una versión más reciente, el PPA
ppa:vbernat/haproxy-3.2mantiene paquetes de HAProxy 3.2 LTS para Ubuntu 24.04. La configuración de esta guía es válida en ambas.
Paso 2: Levantar un backend de prueba
Si ya tienes tu aplicación en el puerto 8080, salta este paso. Si no, sirve un directorio con Python en otra sesión SSH:
mkdir -p ~/backend-demo/static
echo '<h1>Hola desde el backend</h1>' > ~/backend-demo/index.html
echo 'body { color: #333; }' > ~/backend-demo/static/app.css
python3 -m http.server 8080 --bind 127.0.0.1 --directory ~/backend-demo
Este servidor envía la cabecera Last-Modified, que HAProxy necesita para considerar la respuesta cacheable cuando no hay Cache-Control.
Paso 3: Entender qué cachea HAProxy
Antes de configurar nada conviene saber las reglas, porque son más estrictas que las de Nginx o Varnish. HAProxy solo guarda una respuesta cuando se cumple todo lo siguiente:
| Condición | Detalle |
|---|---|
| Método | Solo GET. |
| Versión HTTP | La petición debe ser HTTP/1.1 o superior. |
| Código de estado | Solo 200. |
| Autorización | La petición no puede llevar cabecera Authorization. |
| Caducidad | La respuesta debe tener Cache-Control: max-age o s-maxage, Expires, o un validador (ETag o Last-Modified). |
| Tamaño | Cabeceras más cuerpo no deben superar max-object-size. |
Vary | Sin process-vary on, las respuestas con Vary no se cachean. |
Además, la caché vive en memoria compartida y se pierde al reiniciar HAProxy. Un reload también empieza con la caché vacía.
Paso 4: Definir la caché y activarla
El archivo /etc/haproxy/haproxy.cfg de Ubuntu ya trae las secciones global y defaults, con el socket de administración en /run/haproxy/admin.sock. Haz una copia y ábrelo:
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.orig
sudo nano /etc/haproxy/haproxy.cfg
Añade al final del archivo, sin tocar lo que ya hay:
cache app_cache
total-max-size 256
max-object-size 1048576
max-age 300
frontend web
bind :80
mode http
default_backend app
http-response set-header X-Cache-Status HIT if { res.cache_hit }
http-response set-header X-Cache-Status MISS if !{ res.cache_hit }
backend app
mode http
http-request cache-use app_cache
http-response cache-store app_cache
server app1 127.0.0.1:8080 check
Qué hace cada parte:
total-max-size 256reserva 256 MB de RAM para la caché. El valor se expresa en megabytes y el máximo es 4095.max-object-size 1048576limita cada objeto a 1 MB. Se expresa en bytes y no puede superar la mitad detotal-max-size. Los objetos más grandes simplemente no se cachean.max-age 300es el tiempo máximo en segundos. Si el backend indica unmax-agemenor, se usa el del backend; si indica uno mayor, se recorta a 300. Si no se configura, el valor por defecto es 60 segundos.cache-usebusca la respuesta en la caché antes de enviar la petición al servidor ycache-storeguarda la respuesta al volver.- La muestra
res.cache_hitindica si la respuesta salió de la caché. Se usa en elfrontendpara añadir la cabeceraX-Cache-Statusde diagnóstico.
Valida la configuración antes de aplicarla:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
Configuration file is valid
Recarga el servicio:
sudo systemctl reload haproxy
sudo systemctl status haproxy --no-pager
Paso 5: Comprobar que la caché funciona
Pide la misma página dos veces:
curl -s -o /dev/null -D - http://127.0.0.1/ | grep -iE 'x-cache-status|^age'
curl -s -o /dev/null -D - http://127.0.0.1/ | grep -iE 'x-cache-status|^age'
X-Cache-Status: MISS
X-Cache-Status: HIT
age: 3
HAProxy añade la cabecera Age a las respuestas servidas desde caché, con los segundos que lleva almacenada la entrada. En la terminal del backend de prueba verás que solo llegó la primera petición.
Consulta el contenido de la caché a través del socket de administración:
echo "show cache" | sudo socat stdio /run/haproxy/admin.sock
La salida muestra, para cada caché, los bloques libres y las entradas almacenadas con su tamaño y el tiempo que les queda de vida.
Paso 6: Controlar el TTL y excluir rutas
En una aplicación real no todo debe cachearse igual. Un patrón habitual es cachear solo los recursos estáticos, con un TTL largo, y dejar pasar el resto. Edita el backend:
sudo nano /etc/haproxy/haproxy.cfg
Sustituye la sección backend app por esta versión:
backend app
mode http
acl es_estatico path_beg /static/
acl es_estatico path_end .css .js .png .jpg .svg .woff2
acl sesion req.cook(sessionid) -m found
http-request set-var(txn.cacheable) bool(true) if es_estatico !sesion
http-request cache-use app_cache if { var(txn.cacheable) -m bool }
http-response set-header Cache-Control "public, max-age=300" if { var(txn.cacheable) -m bool } !{ res.hdr(Cache-Control) -m found }
http-response cache-store app_cache if { var(txn.cacheable) -m bool }
server app1 127.0.0.1:8080 check
Cómo funciona:
- Las ACL
es_estaticose definen dos veces con el mismo nombre; HAProxy las combina con un OR, así que basta con que se cumpla una. - La variable
txn.cacheablese marca al recibir la petición cuando es un recurso estático y no lleva la cookie de sesiónsessionid(sustitúyela por la de tu aplicación). Es necesaria porque las muestras de la petición, comopathoreq.cook, no están disponibles en las reglashttp-response: HAProxy avisaría de que la ACL nunca coincide. Las variables con ámbitotxnduran toda la transacción y sí se pueden leer en la respuesta. cache-useycache-storesolo actúan sobre las peticiones marcadas. El resto va siempre al backend y nunca se guarda.- Si el backend no envía
Cache-Controlpara un recurso estático, HAProxy le añademax-age=300antes de guardarlo. Las reglashttp-responsese evalúan en orden, por eso la líneaset-headerva antes decache-store.
Valida y recarga:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy
Comprueba que el CSS se cachea y la página principal no:
curl -s -o /dev/null -D - http://127.0.0.1/static/app.css | grep -i x-cache-status
curl -s -o /dev/null -D - http://127.0.0.1/static/app.css | grep -i x-cache-status
curl -s -o /dev/null -D - http://127.0.0.1/ | grep -i x-cache-status
curl -s -o /dev/null -D - http://127.0.0.1/ | grep -i x-cache-status
X-Cache-Status: MISS
X-Cache-Status: HIT
X-Cache-Status: MISS
X-Cache-Status: MISS
Paso 7 (opcional): Respuestas con cabecera Vary
Si tu backend comprime las respuestas y envía Vary: Accept-Encoding, HAProxy no las cacheará salvo que actives la gestión de variantes. Añade estas líneas a la sección cache app_cache:
process-vary on
max-secondary-entries 10
Con process-vary on, HAProxy guarda una variante por cada valor distinto de las cabeceras que sabe gestionar, como Accept-Encoding. Si el backend usa Vary con otras cabeceras (por ejemplo Cookie o User-Agent), esas respuestas siguen sin cachearse, lo cual es el comportamiento correcto. max-secondary-entries limita cuántas variantes se guardan de una misma URL.
Valida y recarga de nuevo con sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy.
Paso 8: Abrir el puerto en el firewall
Si HAProxy va a recibir tráfico desde internet, permite el puerto 80 (y el 443 cuando configures TLS):
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Para HTTPS, concatena certificado y clave privada en un único archivo PEM y añade bind :443 ssl crt /etc/haproxy/certs/your_domain.pem al frontend. La caché funciona igual con tráfico cifrado porque HAProxy termina el TLS.
Solución de problemas
Siempre MISS. Revisa la tabla del paso 3 contra las cabeceras reales del backend con curl -sI http://127.0.0.1:8080/ruta. Las causas más frecuentes son un código distinto de 200, falta de Cache-Control, Expires y validadores, una cabecera Vary o un cuerpo mayor que max-object-size.
haproxy -c rechaza max-object-size. El valor no puede superar la mitad de total-max-size. Reduce uno o aumenta el otro, y recuerda que max-object-size va en bytes y total-max-size en megabytes.
La caché se vacía sola. Cada systemctl reload o restart arranca con la caché vacía. Es normal: la caché de HAProxy es solo en memoria y está pensada para objetos de vida corta.
show cache devuelve Permission denied. Ejecuta socat con sudo, o añade tu usuario al grupo haproxy, dueño del socket.
Conclusión
HAProxy ya cachea en memoria los recursos estáticos de tu aplicación, respeta el TTL que envía el backend con un máximo de 300 segundos y deja pasar las peticiones con sesión. Como siguientes pasos puedes añadir más servidores al backend para balancear carga, activar la página de estadísticas de HAProxy para vigilar el tráfico o colocar Varnish o una CDN delante si necesitas una caché grande en disco con purga selectiva.
