Dia 16 - Configurar Nginx como Load Balancer
Problema / Desafio
El trafico de un sitio web ha aumentado y el equipo ha decidido desplegar la aplicacion en alta disponibilidad usando los 3 app servers de Stratos DC. La migracion esta casi completa, solo falta configurar el servidor LBR (Load Balancer):
- Instalar Nginx en el servidor LBR
- Configurar load balancing en el contexto
httpusando los 3 app servers - Solo modificar
/etc/nginx/nginx.conf - No cambiar el puerto de Apache (3001) en los app servers
- Asegurar que Apache este corriendo en los 3 app servers
Conceptos clave
Load Balancer
Un Load Balancer distribuye el trafico entrante entre multiples servidores para:
- Alta disponibilidad — si un servidor cae, los otros siguen atendiendo
- Escalabilidad — distribuir la carga entre multiples servidores
- Rendimiento — ningun servidor se satura con todo el trafico
Sin Load Balancer:
Cliente → stapp01:3001 ← Todo el trafico va a un solo servidor
Con Load Balancer:
┌→ stapp01:3001
Cliente → LBR:80 ──┼→ stapp02:3001
└→ stapp03:3001
Nginx como reverse proxy / load balancer
Nginx no solo sirve paginas web — tambien puede actuar como reverse proxy que recibe peticiones y las reenvia a servidores backend:
| Rol | Funcion |
|---|---|
| Web server | Sirve archivos estaticos directamente |
| Reverse proxy | Reenvia peticiones a otro servidor |
| Load balancer | Reverse proxy que distribuye entre multiples servidores |
Directiva upstream
El bloque upstream en Nginx define un grupo de servidores backend entre los cuales se distribuye el trafico:
Nginx usa este grupo como destino en proxy_pass.
Algoritmos de balanceo
| Algoritmo | Directiva | Comportamiento |
|---|---|---|
| Round Robin (default) | (ninguna) | Distribuye peticiones secuencialmente: 1→2→3→1→2→3... |
| Least Connections | least_conn; |
Envia al servidor con menos conexiones activas |
| IP Hash | ip_hash; |
El mismo cliente siempre va al mismo servidor (session persistence) |
| Weighted | server host weight=3; |
Servidores con mayor peso reciben mas trafico |
Para este ejercicio usamos Round Robin (default) que es el mas simple y funciona bien cuando los servidores tienen capacidad similar.
Diferencia entre proxy_pass y root
| Directiva | Funcion | Ejemplo |
|---|---|---|
root |
Nginx sirve archivos locales del filesystem | root /usr/share/nginx/html; |
proxy_pass |
Nginx reenvia la peticion a otro servidor | proxy_pass http://backend; |
En modo load balancer, Nginx no tiene archivos locales — solo reenvia trafico.
Pasos
- Verificar que Apache esta corriendo en los 3 app servers
- Instalar Nginx en el servidor LBR
- Configurar Nginx como load balancer en
/etc/nginx/nginx.conf - Validar la configuracion y reiniciar Nginx
- Probar el acceso al sitio web
Comandos / Codigo
1. Verificar Apache en los app servers
Desde el jump host, verificar que los 3 app servers responden en el puerto 3001:
for host in stapp01 stapp02 stapp03; do
echo -n "$host: "
curl -s -o /dev/null -w "%{http_code}" $host:3001
echo
done
Si alguno no responde, conectarse por SSH y levantar Apache:
2. Instalar Nginx en el LBR
ssh lbr_user@lbr
# Instalar Nginx
sudo yum install -y epel-release
sudo yum install -y nginx
# Habilitar e iniciar
sudo systemctl enable nginx
sudo systemctl start nginx
3. Configurar el load balancing
Editar el archivo de configuracion principal:
Configuracion completa:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
# Grupo de servidores backend
upstream backend {
server stapp01.stratos.xfusioncorp.com:3001;
server stapp02.stratos.xfusioncorp.com:3001;
server stapp03.stratos.xfusioncorp.com:3001;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
Explicacion de cada bloque:
nginx.conf
├── worker_processes auto # Numero de workers (auto = 1 por CPU)
├── events
│ └── worker_connections 1024 # Conexiones simultaneas por worker
└── http
├── upstream backend # Define los 3 app servers como grupo
│ ├── stapp01:3001
│ ├── stapp02:3001
│ └── stapp03:3001
└── server
├── listen 80 # LBR escucha en puerto 80
└── location /
└── proxy_pass http://backend # Reenvia a los app servers
4. Validar y reiniciar
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
5. Verificar el load balancing
Para verificar que el round robin funciona, hacer multiples peticiones y ver los logs de cada app server:
# Multiples peticiones
for i in $(seq 1 6); do curl -s http://lbr:80 > /dev/null; done
# Revisar logs en cada app server
ssh tony@stapp01 "sudo tail -3 /var/log/httpd/access_log"
ssh steve@stapp02 "sudo tail -3 /var/log/httpd/access_log"
ssh banner@stapp03 "sudo tail -3 /var/log/httpd/access_log"
Deberia verse trafico distribuido entre los 3 servidores.
Configuraciones adicionales para produccion
Headers del proxy
En produccion, los app servers necesitan saber la IP real del cliente (no la del LBR):
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
| Header | Funcion |
|---|---|
Host |
Nombre del host original que el cliente solicito |
X-Real-IP |
IP real del cliente |
X-Forwarded-For |
Cadena de IPs por las que paso la peticion (cliente → proxies) |
X-Forwarded-Proto |
Protocolo original (http o https) |
Sin estos headers, los app servers ven todas las peticiones como si vinieran del LBR.
Health checks pasivos
Nginx detecta automaticamente servidores caidos y deja de enviarles trafico:
upstream backend {
server stapp01:3001 max_fails=3 fail_timeout=30s;
server stapp02:3001 max_fails=3 fail_timeout=30s;
server stapp03:3001 max_fails=3 fail_timeout=30s;
}
| Parametro | Funcion |
|---|---|
max_fails=3 |
Despues de 3 intentos fallidos, marca el servidor como caido |
fail_timeout=30s |
Espera 30 segundos antes de reintentar con el servidor caido |
Servidor de backup
upstream backend {
server stapp01:3001;
server stapp02:3001;
server stapp03:3001;
server stapp04:3001 backup; # Solo recibe trafico si los otros 3 caen
}
Weighted round robin
Si un servidor tiene mas capacidad que los otros:
upstream backend {
server stapp01:3001 weight=3; # Recibe 3x mas trafico
server stapp02:3001 weight=1;
server stapp03:3001 weight=1;
}
Session persistence con ip_hash
Si la aplicacion usa sesiones (login, carrito de compras), el mismo cliente debe ir siempre al mismo servidor:
Configuraciones tipicas de Nginx
Rate limiting
Limitar el numero de peticiones por segundo para prevenir abuso o DDoS:
http {
# Definir zona de rate limiting: 10 peticiones por segundo por IP
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location / {
# Aplicar el rate limit con burst de 20 (cola de espera)
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://backend;
}
}
}
| Parametro | Funcion |
|---|---|
rate=10r/s |
Maximo 10 peticiones por segundo por IP |
burst=20 |
Permite rafagas de hasta 20 peticiones (las excedentes se encolan) |
nodelay |
Procesa las peticiones del burst inmediatamente (sin delay artificial) |
zone=mylimit:10m |
10 MB de memoria compartida para tracking (~160,000 IPs) |
Timeouts
Configurar timeouts para evitar conexiones colgadas:
http {
# Timeouts del proxy hacia los backend servers
proxy_connect_timeout 5s; # Timeout para establecer conexion con backend
proxy_send_timeout 10s; # Timeout para enviar datos al backend
proxy_read_timeout 30s; # Timeout para recibir respuesta del backend
# Timeouts del cliente
client_body_timeout 10s; # Timeout para recibir el body del cliente
client_header_timeout 5s; # Timeout para recibir los headers del cliente
send_timeout 10s; # Timeout para enviar respuesta al cliente
# Keepalive
keepalive_timeout 65s; # Tiempo que mantiene la conexion abierta
}
Limitar tamano de uploads
Si se excede el limite, Nginx devuelve 413 Request Entity Too Large.
Gzip compression
Comprimir las respuestas para reducir el ancho de banda:
http {
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000; # No comprimir archivos menores a 1KB
gzip_comp_level 6; # Nivel de compresion (1-9, 6 es buen balance)
gzip_vary on; # Agregar header Vary: Accept-Encoding
}
Caching de archivos estaticos
server {
# Cache de archivos estaticos por 30 dias
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Logs personalizados
http {
# Formato de log personalizado
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream: $upstream_addr response_time: $upstream_response_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
}
Variables utiles para logs de load balancer:
| Variable | Valor |
|---|---|
$upstream_addr |
IP del backend que atendio la peticion |
$upstream_response_time |
Tiempo de respuesta del backend |
$upstream_status |
HTTP status del backend |
$request_time |
Tiempo total de la peticion (cliente → nginx → backend → cliente) |
Seguridad basica
server {
# Ocultar version de Nginx en headers y paginas de error
server_tokens off;
# Bloquear acceso a archivos ocultos (.git, .env, .htaccess)
location ~ /\. {
deny all;
return 404;
}
# Bloquear acceso a archivos de backup
location ~ ~$ {
deny all;
}
}
server_tokens off cambia el header de nginx/1.20.1 a solo nginx, ocultando la version exacta.
Ejemplo de nginx.conf completo para produccion
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logs
log_format main '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent '
'upstream=$upstream_addr time=$upstream_response_time';
access_log /var/log/nginx/access.log main;
# Performance
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# Seguridad
server_tokens off;
client_max_body_size 10M;
# Rate limiting
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
# Backend servers
upstream backend {
server stapp01:3001 max_fails=3 fail_timeout=30s;
server stapp02:3001 max_fails=3 fail_timeout=30s;
server stapp03:3001 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name _;
location / {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
location ~ /\. {
deny all;
return 404;
}
}
}
Troubleshooting
| Problema | Solucion |
|---|---|
502 Bad Gateway |
Los app servers no responden. Verificar que Apache esta corriendo en los 3 con curl stapp0X:3001 |
nginx -t falla |
Verificar sintaxis de nginx.conf. Los bloques upstream deben estar dentro de http {} |
| Solo responde un servidor | Verificar que los 3 servers estan listados en upstream. Revisar que no haya ip_hash activo |
| Timeout al acceder al LBR | Verificar que Nginx esta corriendo: systemctl status nginx. Verificar firewall del LBR |
| App servers ven IP del LBR en logs | Agregar proxy_set_header X-Real-IP $remote_addr en la configuracion de location |
upstream fuera de http causa error |
El bloque upstream debe estar dentro del bloque http {}, no fuera |