Balanceo de carga básico con Nginx

Capítulo 7

Tiempo estimado de lectura: 7 minutos

+ Ejercicio

¿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):

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

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 3001
python3 -m http.server 3002

Ambos 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 -t
sudo nginx -s reload

Paso 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; done

Si 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.log

Busca 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/; done

Si 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:3002 con up_status vacío o códigos de error/reintentos.
  • Transición a usar casi exclusivamente 127.0.0.1:3001 durante el periodo de fail_timeout.
grep 'upstream=' /var/log/nginx/lb_access.log | tail -n 30

Paso 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 cuandoEvítalo cuando
Round-robinInstancias similares, tráfico general, simplicidadNecesitas afinidad por cliente o cargas muy desbalanceadas por duración
least_connPeticiones con tiempos muy variables, riesgo de “colas” en un backendTu carga es homogénea y quieres comportamiento más predecible
ip_hashNecesitas sticky sessions por IP (estado en memoria local)Muchos clientes comparten IP (NAT/proxy) o buscas distribución uniforme

Ahora responde el ejercicio sobre el contenido:

Si tu aplicación guarda estado en memoria local y necesitas que un mismo cliente sea dirigido casi siempre al mismo backend, ¿qué algoritmo de balanceo conviene configurar en Nginx?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

ip_hash busca que las solicitudes de un cliente (según su IP) lleguen al mismo backend, lo que ayuda a mantener sesiones pegajosas cuando el estado se guarda en memoria local y no hay un almacén compartido.

Siguiente capítulo

Seguridad esencial en Nginx: TLS, cabeceras y restricciones

Arrow Right Icon
Portada de libro electrónico gratuitaNginx para Principiantes: Domina el Servidor Web Moderno desde Cero
70%

Nginx para Principiantes: Domina el Servidor Web Moderno desde Cero

Nuevo curso

10 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.