¿Qué es el balanceo de carga en Nginx?
El balanceo de carga consiste en distribuir solicitudes entrantes entre varios servidores backend (instancias de tu aplicación) para mejorar capacidad, latencia y tolerancia a fallos. En Nginx, el mecanismo básico se configura con un bloque upstream (grupo de backends) y un proxy_pass que envía tráfico a ese grupo.
El bloque upstream: agrupando backends
Un upstream define una lista de servidores destino. Luego, desde un location haces proxy_pass http://NOMBRE_UPSTREAM;. Ejemplo mínimo:
upstream app_backend { server 127.0.0.1:3001; server 127.0.0.1:3002;}server { listen 80; server_name _; location / { proxy_pass http://app_backend; }}En este punto, Nginx ya reparte tráfico usando el algoritmo por defecto (round-robin).
Algoritmos principales y cuándo usarlos
1) Round-robin (por defecto)
Distribuye solicitudes de forma rotativa entre los backends. Es una buena opción general cuando tus instancias son similares (misma capacidad) y no necesitas “pegajosidad” (sticky sessions).
Ejemplo (no hace falta declarar nada especial):
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
upstream app_backend { server 127.0.0.1:3001; server 127.0.0.1:3002;}2) least_conn
Envía nuevas solicitudes al backend con menos conexiones activas. Útil cuando las peticiones tienen duraciones muy variables (por ejemplo, endpoints que a veces tardan mucho), o cuando un backend puede quedar “ocupado” más tiempo que otro.
upstream app_backend { least_conn; server 127.0.0.1:3001; server 127.0.0.1:3002;}3) ip_hash
Intenta que un mismo cliente (según su IP) caiga siempre en el mismo backend. Se usa para sesiones “pegajosas” cuando la aplicación guarda estado en memoria local y no hay un almacén compartido (idealmente, deberías externalizar sesión/estado, pero cuando no es posible, ip_hash ayuda).
Consideraciones: si muchos usuarios salen por una misma IP (NAT corporativo, proxies), se puede concentrar carga en un backend. También, si un backend cae, parte de los clientes se reasignarán.
upstream app_backend { ip_hash; server 127.0.0.1:3001; server 127.0.0.1:3002;}Health checks pasivos: detectar fallos por errores
En Nginx Open Source, el “health check” básico es pasivo: Nginx marca un backend como no disponible cuando observa fallos al intentar conectarse o leer respuesta. Esto se controla con parámetros por servidor como max_fails y fail_timeout.
max_fails: número de fallos permitidos durante una ventana antes de considerar el backend “caído”.fail_timeout: ventana de tiempo para contar fallos y, una vez marcado como caído, cuánto tiempo se evita ese backend antes de reintentar.
Ejemplo típico:
upstream app_backend { server 127.0.0.1:3001 max_fails=3 fail_timeout=10s; server 127.0.0.1:3002 max_fails=3 fail_timeout=10s;}Qué se considera “fallo” en este contexto: errores de conexión al backend, timeouts, y ciertos errores al leer la respuesta. En la práctica, si una instancia se cae o deja de aceptar conexiones, Nginx empezará a evitarla tras superar el umbral.
Configuración recomendada para observar y depurar
Para poder confirmar distribución y aislar problemas, conviene enriquecer los logs con el upstream elegido y el estado de respuesta.
Log con datos del upstream
Define un formato de log que incluya variables de upstream (en el bloque http), y úsalo en tu access_log:
log_format lb '$remote_addr - $request ' 'status=$status upstream=$upstream_addr ' 'up_status=$upstream_status rt=$request_time ' 'urt=$upstream_response_time';Y en tu servidor:
access_log /var/log/nginx/access.log lb;Variables útiles:
$upstream_addr: backend al que se envió la solicitud (IP:puerto).$upstream_status: código(s) devuelto(s) por el upstream (puede ser lista si hubo reintentos).$upstream_response_time: tiempo(s) de respuesta del upstream.
Timeouts razonables para detectar backends colgados
Si un backend se queda colgado, los timeouts ayudan a que Nginx falle rápido y pueda intentar otro backend (según el caso):
server { listen 80; server_name _; location / { proxy_connect_timeout 2s; proxy_read_timeout 5s; proxy_send_timeout 5s; proxy_next_upstream error timeout http_502 http_503 http_504; proxy_pass http://app_backend; }}proxy_next_upstream indica en qué condiciones Nginx puede intentar otro backend. Esto es útil para resiliencia, pero también puede ocultar fallos si no lo observas en logs (por eso es importante registrar $upstream_addr y $upstream_status).
Práctica guiada: dos instancias en puertos distintos y ver la distribución
Objetivo: levantar dos backends locales en puertos diferentes, balancearlos con Nginx y comprobar que las solicitudes se reparten. Luego, simular un fallo y verificar que Nginx aísla el backend defectuoso mediante health checks pasivos.
Paso 1: levantar dos backends de prueba
Ejemplo con Python (rápido y suficiente para la práctica). En dos terminales distintas, ejecuta:
python3 -m http.server 3001python3 -m http.server 3002Ambos servirán contenido del directorio actual. Para distinguirlos fácilmente, crea en cada directorio un archivo index.html distinto (por ejemplo, uno que diga “backend 3001” y otro “backend 3002”).
Paso 2: configurar Nginx con upstream
Crea (o edita) un archivo de configuración del sitio y define el upstream. Ejemplo con round-robin y health checks pasivos:
upstream app_backend { server 127.0.0.1:3001 max_fails=2 fail_timeout=10s; server 127.0.0.1:3002 max_fails=2 fail_timeout=10s;}server { listen 8080; server_name _; access_log /var/log/nginx/lb_access.log lb; location / { proxy_connect_timeout 1s; proxy_read_timeout 3s; proxy_next_upstream error timeout http_502 http_503 http_504; proxy_pass http://app_backend; }}Valida y recarga Nginx:
nginx -tsudo nginx -s reloadPaso 3: confirmar distribución con múltiples solicitudes
Haz varias solicitudes y observa si alterna el backend (round-robin). Por ejemplo:
for i in $(seq 1 10); do curl -s http://127.0.0.1:8080/ | head -n 1; doneSi tus index.html son distintos, deberías ver alternancia. Si el contenido es igual, confirma por logs (siguiente paso).
Paso 4: observar resultados en logs
Inspecciona el archivo de log configurado:
tail -n 50 /var/log/nginx/lb_access.logBusca campos como upstream=127.0.0.1:3001 y upstream=127.0.0.1:3002. Deberías ver cómo se reparten las solicitudes. Si hay reintentos, podrías ver múltiples valores en $upstream_addr o $upstream_status.
Paso 5: cambiar el algoritmo y comparar
Prueba least_conn:
upstream app_backend { least_conn; server 127.0.0.1:3001 max_fails=2 fail_timeout=10s; server 127.0.0.1:3002 max_fails=2 fail_timeout=10s;}Recarga Nginx y repite el bucle de curl. Para notar diferencias con least_conn, simula carga manteniendo conexiones abiertas (por ejemplo, descargando un archivo grande o usando un endpoint que tarde). En escenarios reales, least_conn se aprecia cuando hay peticiones lentas mezcladas con rápidas.
Prueba ip_hash:
upstream app_backend { ip_hash; server 127.0.0.1:3001 max_fails=2 fail_timeout=10s; server 127.0.0.1:3002 max_fails=2 fail_timeout=10s;}Desde el mismo cliente (misma IP), deberías observar que casi siempre se elige el mismo backend. Confírmalo mirando $upstream_addr en el log.
Aislar un backend defectuoso (simulación de caída)
Paso 1: apagar una instancia
Detén el backend del puerto 3002 (Ctrl+C en esa terminal). Ahora el upstream 3002 fallará al conectar.
Paso 2: generar solicitudes y observar el aislamiento
Ejecuta varias solicitudes:
for i in $(seq 1 20); do curl -s -o /dev/null -w "status=%{http_code}\n" http://127.0.0.1:8080/; doneSi tienes proxy_next_upstream habilitado para error/timeout, muchas solicitudes seguirán devolviendo 200 porque Nginx reintentará con el backend sano. En el log, verás intentos al backend caído y luego al sano, o directamente que deja de usar el caído tras superar max_fails dentro de fail_timeout.
Paso 3: confirmar en logs qué backend falló
Revisa el log y busca:
upstream=127.0.0.1:3002conup_statusvacío o códigos de error/reintentos.- Transición a usar casi exclusivamente
127.0.0.1:3001durante el periodo defail_timeout.
grep 'upstream=' /var/log/nginx/lb_access.log | tail -n 30Paso 4: volver a habilitar el backend
Vuelve a levantar el backend en 3002 y espera a que pase fail_timeout (o genera tráfico y observa cuándo Nginx lo reintroduce). Verás en logs que vuelve a aparecer 127.0.0.1:3002 como destino.
Criterios rápidos de elección (resumen operativo)
| Algoritmo | Úsalo cuando | Evítalo cuando |
|---|---|---|
| Round-robin | Instancias similares, tráfico general, simplicidad | Necesitas afinidad por cliente o cargas muy desbalanceadas por duración |
| least_conn | Peticiones con tiempos muy variables, riesgo de “colas” en un backend | Tu carga es homogénea y quieres comportamiento más predecible |
| ip_hash | Necesitas sticky sessions por IP (estado en memoria local) | Muchos clientes comparten IP (NAT/proxy) o buscas distribución uniforme |