Cómo hacer invisible un reinicio de backend para sus visitantes
Dos servidores web siguen mostrando errores cuando uno se reinicia. Los ajustes de HAProxy, Varnish y keepalived que hacen invisible un reinicio, y cómo probarlo.

Tener dos servidores web detrás de un balanceador no significa que un reinicio sea invisible. Significa que un reinicio produce una ráfaga corta de errores en lugar de una caída. La diferencia entre «corta» y «ninguna» vive en unos pocos ajustes en el edge, y en probarlos con tráfico real antes de confiar en ellos. Así cerramos nosotros esa brecha.
Por qué la redundancia por sí sola sigue mostrando errores
Cuando PHP-FPM o nginx se reinician en un nodo web, ocurren tres cosas en el segundo o dos que tarda. Las peticiones ya en vuelo en ese nodo se cortan. Siguen llegando peticiones nuevas, porque el balanceador todavía no se ha dado cuenta de que el nodo se ha ido. Y cuando el nodo vuelve, puede recibir una cuota completa de tráfico antes de que sus cachés y su caché de opcodes estén calientes.
Un balanceador con ajustes por defecto no gestiona nada de esto con elegancia. Su comprobación de salud se ejecuta cada pocos segundos y necesita varios fallos antes de marcar un nodo como caído, así que durante esa ventana está enviando deliberadamente visitantes a un servidor que no responde. Cada una de esas peticiones se convierte en un 502 o un 503 en la pantalla de alguien.
Reintento y redispatch en el edge
La primera corrección es dejar que HAProxy lo intente de nuevo, en otro sitio, cuando una conexión falla:
defaults
mode http
retries 3
option redispatch 1
retry-on conn-failure empty-response response-timeout
timeout connect 3s
retries permite a HAProxy reintentar una petición cuya conexión falló. option redispatch 1 hace que el reintento vaya a un servidor distinto en lugar de insistir en el que acaba de rechazarla. retry-on extiende los reintentos más allá de las conexiones rechazadas a las respuestas vacías y los tiempos de espera agotados, que es lo que suele producir un proceso a medio reinicio.
Los reintentos son seguros para las peticiones que no se han procesado. Para las peticiones POST que pueden haber llegado a la aplicación, HAProxy es conservador por defecto, y eso es lo que usted quiere: un pago nunca debería enviarse dos veces.
Comprobaciones de salud que ven la aplicación, no el puerto
Una comprobación TCP pasa mientras nginx esté escuchando, incluso cuando todos los workers de PHP están atascados. Compruebe algo que ejercite la pila:
backend be_web
option httpchk GET /healthz
http-check expect status 200
default-server inter 2s fall 3 rise 2 slowstart 20s
server web1 <web1>:80 check
server web2 <web2>:80 check
El endpoint /healthz debería ser un pequeño script PHP que toque lo que importa (abre una conexión a la base de datos, lee una clave de la caché) y responda deprisa. fall 3 significa que una comprobación lenta durante una rotación de logs o una recarga no expulsa a un nodo sano; rise 2 mantiene fuera a un nodo hasta que responda dos veces. slowstart aumenta gradualmente el tráfico de un servidor que vuelve, de modo que un nodo con las cachés frías no recibe de golpe una cuota completa de tráfico.
Drene antes de reiniciar
El reinicio más limpio es el que el balanceador conoce de antemano. La API en tiempo de ejecución de HAProxy le permite sacar un nodo de la rotación sin cortar las peticiones establecidas:
echo "set server be_web/web1 state drain" | socat stdio /run/haproxy/admin.sock
# wait for current sessions to finish
echo "show servers state be_web" | socat stdio /run/haproxy/admin.sock
systemctl restart php-fpm # on web1
echo "set server be_web/web1 state ready" | socat stdio /run/haproxy/admin.sock
Envuelva eso en un script y cada reinicio planificado (una actualización de seguridad de PHP, un cambio de configuración, un reinicio del kernel) se convierte en drenar, esperar, reiniciar, verificar, devolver, un nodo cada vez.
Deje que la caché cubra el hueco
Una caché de página completa hace más que acelerar el tráfico anónimo. Con grace configurado, Varnish sigue sirviendo la última copia buena de una página cuando no puede obtener una fresca:
import std;
sub vcl_recv {
if (std.healthy(req.backend_hint)) {
set req.grace = 10s;
}
}
sub vcl_backend_response {
set beresp.ttl = 5m;
set beresp.grace = 24h;
}
Mientras los backends están sanos, los objetos se refrescan a los pocos segundos de caducar. Cuando todos los backends están enfermos, Varnish puede servir objetos hasta un día después de su TTL. Los lectores siguen recibiendo páginas durante un mantenimiento de la base de datos o un pool de PHP atascado, y las rutas de usuarios identificados y de pago, que evitan la caché por diseño, son las únicas que dependen de que los backends estén arriba.
El propio edge necesita un compañero
Dos nodos edge que ejecutan keepalived comparten direcciones flotantes, y un script de seguimiento degrada a un edge cuyo HAProxy se ha detenido:
vrrp_script chk_haproxy {
script "/usr/sbin/pidof haproxy"
interval 2
fall 2
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
track_script { chk_haproxy }
virtual_ipaddress { <floating address>/<prefix> }
}
Construimos los edges cruzados: cada nodo es primario para una dirección pública y standby para la otra, con el registro DNS llevando ambas. Los dos edges llevan tráfico todo el tiempo, y un fallo mueve una dirección al superviviente en segundos.
Demuéstrelo con tráfico, no con una sensación
Un reinicio solo es invisible si usted lo ha visto ser invisible. La prueba que ejecutamos antes de la entrega es sencilla:
- Ponga una carga constante sobre el sitio desde fuera, con una mezcla de URL en caché y sin caché. Un pequeño generador de carga o un bucle de curl basta.
- Cuente las respuestas por código de estado en el cliente, con marca de tiempo.
- Reinicie PHP-FPM en un nodo web. Después en el otro. Después haga fallar un edge.
- Compare el recuento del lado del cliente con los logs del origen.
while true; do
printf '%s ' "$(date +%T.%N)"
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://www.example.com/
sleep 0.2
done | tee restart-test.log
grep -vc ' 200 ' restart-test.log
El origen normalmente registrará un puñado de errores durante cada reinicio: ese es el hueco que el edge debe absorber. Lo que importa es el número del lado del cliente. Cuando el reintento, el redispatch y el grace funcionan, el recuento de los visitantes se queda en nada mientras el origen muestra su breve hueco de reinicio. Si algún error llega al cliente, las marcas de tiempo le dicen qué capa lo dejó pasar.
Su lista de comprobación
- HAProxy con reintentos y redispatch, y retry-on cubriendo respuestas vacías y tiempos de espera.
- Una comprobación de salud HTTP que toque PHP y la base de datos, con fall y rise ajustados contra el flapping.
- Reinicios planificados que drenan primero, a través de la API en tiempo de ejecución.
- Grace de Varnish lo bastante largo para cubrir su mantenimiento de backend más largo.
- Dos edges con direcciones flotantes y un script de seguimiento, ambos llevando tráfico.
- Una prueba de reinicio ejecutada bajo carga, con el recuento de errores del lado del cliente anotado.
Si quiere ayuda para afinar una pila hasta que sus reinicios desaparezcan, nosotros hacemos esto cada semana.
CTA
Related
Relacionado: Capas web de alta disponibilidad, N+1 en cada nivel · Caché de objetos con failover automático · Control de tráfico en el edge para crawlers y scrapers
Publicado el 27 de agosto de 2026.
Todas las notas de campo