ALB Devuelve 502 Bad Gateway: Diagnóstico Cuando las Instancias Aparecen como Saludables
El 502 de un Application Load Balancer con instancias marcadas como 'Healthy' es uno de los escenarios más frustrantes en producción: el health check pasa, el target group parece correcto, pero el tráfico real falla. El problema no está en la disponibilidad del backend — está en lo que el backend responde cuando recibe una petición HTTP real.
TL;DR: Causas Comunes del 502 en ALB
| Causa | Síntoma Observable | Capa Afectada |
|---|---|---|
| Respuesta HTTP malformada del backend | 502 inmediato, sin latencia | Aplicación |
| Conexión cerrada prematuramente por el backend | 502 con target_processing_time muy bajo | TCP/Aplicación |
| Keep-alive timeout desalineado | 502 intermitente bajo carga | TCP |
| Respuesta excede límites de cabeceras HTTP | 502 en rutas específicas con cabeceras grandes | Aplicación |
| Backend cierra conexión durante la respuesta | 502 con target_processing_time parcial | Aplicación/Red |
Cómo Funciona la Conexión ALB–Backend
El ALB actúa como proxy HTTP completo. Termina la conexión TLS del cliente, parsea la petición HTTP, y abre una conexión separada hacia el target. Si el target responde con algo que el ALB no puede interpretar como una respuesta HTTP válida — cabeceras malformadas, conexión cerrada antes de enviar el status line, o un body truncado — el ALB emite un 502 hacia el cliente.
El health check, por diseño, es una petición HTTP simple y periódica. Pasa aunque la aplicación falle en el 80% de las peticiones reales. Un target puede estar 'Healthy' y aun así producir 502s en producción si el fallo es específico a ciertas rutas, cabeceras, o condiciones de carga.
- Cliente → ALB: El ALB termina la conexión TLS y parsea la petición HTTP completa.
- ALB → Target: El ALB abre una conexión hacia el backend y reenvía la petición.
- Target → ALB: Si la respuesta es inválida, truncada, o la conexión se cierra prematuramente, el ALB genera un 502.
- Health Check (paralelo): El health check corre independientemente sobre una ruta simple — no detecta fallos en rutas de producción.
Paso 1: Leer los Access Logs del ALB para Identificar el Patrón del 502
Antes de tocar cualquier configuración, necesitas datos. Los access logs del ALB contienen los campos críticos para distinguir entre un fallo de conexión, un timeout, o una respuesta malformada. Habilítalos si no están activos — sin ellos estás adivinando.
Habilitar access logs en el ALB:
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--attributes Key=access_logs.s3.enabled,Value=true \
Key=access_logs.s3.bucket,Value=my-alb-logs-bucket \
Key=access_logs.s3.prefix,Value=my-alb
Una vez que los logs estén en S3, descarga una muestra y filtra los 502s. El formato de log del ALB es texto delimitado por espacios. Los campos relevantes son: posición 7 es target_processing_time, posición 9 es el código de respuesta del ALB, posición 10 es el código de respuesta del target, y posición 11 es el bytes recibidos del target.
# Descargar logs desde S3
aws s3 cp s3://my-alb-logs-bucket/my-alb/ ./alb-logs/ --recursive --include '*.log.gz'
# Descomprimir y filtrar 502s mostrando: target_processing_time, elb_status_code, target_status_code, received_bytes
zcat ./alb-logs/*.log.gz | awk '$9 == 502 {print $7, $9, $10, $11}' | head -50
Lo que buscas en la salida:
target_processing_timede-1o muy cercano a 0: el backend cerró la conexión antes de responder.target_status_codede-(guión): el ALB no recibió ninguna respuesta HTTP válida del target.target_status_codecon un valor real (ej. 200): el backend respondió, pero la respuesta estaba malformada.
Paso 2: Verificar el Protocolo del Target Group
Una causa silenciosa que aparece frecuentemente: el target group está configurado en HTTP pero el backend escucha en HTTPS, o viceversa. El ALB establece la conexión, el backend responde con un handshake TLS donde el ALB espera un status line HTTP — el resultado es un 502 inmediato con target_status_code: -.
aws elbv2 describe-target-groups \
--target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
--query 'TargetGroups[*].{Protocol:Protocol,Port:Port,HealthCheckProtocol:HealthCheckProtocol,HealthCheckPath:HealthCheckPath}'
Verifica que el protocolo del target group coincida exactamente con lo que escucha tu aplicación. Si el target group dice HTTP y tu app escucha en HTTPS, el health check puede pasar si también está configurado en HTTP hacia un puerto diferente — pero el tráfico real fallará.
Paso 3: Diagnosticar el Keep-Alive Timeout
Este es el fallo que más tiempo cuesta diagnosticar porque es intermitente y no deja rastro en los logs de la aplicación. El ALB mantiene conexiones persistentes hacia los targets. El timeout de keep-alive del ALB es configurable, con un valor por defecto de 60 segundos. Si el backend cierra la conexión antes de que el ALB la considere expirada, el ALB intenta reutilizar una conexión ya cerrada y obtiene un reset TCP — que se traduce en un 502.
Es como intentar hablar por un teléfono que el otro lado ya colgó. El ALB no sabe que la línea está muerta hasta que intenta enviar algo.
Verifica el idle timeout actual del ALB:
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--query 'Attributes[?Key==`idle_timeout.timeout_seconds`]'
La regla operativa: el keep-alive timeout del backend debe ser mayor que el idle timeout del ALB. Si el ALB tiene 60 segundos y Nginx tiene keepalive_timeout 60, hay una condición de carrera. Configura el backend a 75 segundos o más, o reduce el idle timeout del ALB.
Ajustar el idle timeout del ALB si es necesario:
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--attributes Key=idle_timeout.timeout_seconds,Value=50
Paso 4: Reproducir el Error Directamente contra el Backend
Los pasos anteriores identifican el patrón — este paso confirma la causa. Conéctate a una instancia en la misma VPC y envía la misma petición que el ALB enviaría. Si el backend responde correctamente aquí pero el ALB devuelve 502, el problema está en la capa de protocolo entre ALB y backend. Si falla aquí también, el problema es la aplicación.
# Desde una instancia en la misma VPC, usando la IP privada del target
curl -v --http1.1 \
-H 'Host: www.ejemplo.com' \
-H 'X-Forwarded-For: 1.2.3.4' \
http://10.0.1.50:8080/ruta-que-falla
Observa la salida de curl -v con atención. Una respuesta HTTP válida comienza con HTTP/1.1 200 o similar. Si ves conexión rechazada, reset TCP, o una respuesta que no empieza con un status line HTTP, encontraste el problema.
Para verificar que el backend no está enviando cabeceras malformadas — por ejemplo, cabeceras con caracteres inválidos o sin el separador correcto — captura la respuesta raw:
# Ver la respuesta HTTP raw sin procesamiento
nc -q 3 10.0.1.50 8080 << 'EOF'
GET /ruta-que-falla HTTP/1.1
Host: www.ejemplo.com
Connection: close
EOF
Paso 5: Revisar el Security Group del Target
Aunque las instancias estén 'Healthy', el health check y el tráfico de producción pueden usar puertos diferentes si la configuración es inconsistente. Un Security Group que permite el puerto del health check pero bloquea el puerto de producción produce exactamente este síntoma: targets healthy, 502 en tráfico real.
# Obtener el Security Group ID de las instancias target
aws ec2 describe-instances \
--instance-ids i-1234567890abcdef0 \
--query 'Reservations[*].Instances[*].SecurityGroups'
# Verificar las reglas de entrada del Security Group
aws ec2 describe-security-groups \
--group-ids sg-1234567890abcdef0 \
--query 'SecurityGroups[*].IpPermissions'
Confirma que el Security Group del target permite tráfico entrante desde el Security Group del ALB en el puerto exacto que usa el target group — no solo el puerto del health check.
Experiencia de Campo: El 502 que No Era un 502
En un sistema con un backend Node.js detrás de un ALB, los 502s aparecían exclusivamente en horas de baja carga — nunca durante el pico. Los logs de la aplicación no mostraban ningún error. El equipo pasó dos días revisando la aplicación.
El access log del ALB mostraba target_processing_time: 0.000 y target_status_code: -. Eso es una conexión cerrada antes de que el backend respondiera — no un error de la aplicación.
La causa real: Node.js por defecto cierra conexiones keep-alive después de 5 segundos de inactividad (server.keepAliveTimeout). El ALB tenía un idle timeout de 60 segundos. En horas de baja carga, las conexiones permanecían idle más de 5 segundos entre peticiones. El ALB intentaba reutilizar una conexión que Node.js ya había cerrado silenciosamente.
La corrección fue aumentar server.keepAliveTimeout en Node.js a 65000 milisegundos y server.headersTimeout a 66000 milisegundos. Los 502s desaparecieron inmediatamente. El health check nunca detectó el problema porque las peticiones de health check llegaban con suficiente frecuencia para mantener las conexiones activas.
- Hora de baja carga: Las conexiones entre ALB y Node.js permanecen idle más de 5 segundos.
- Node.js cierra la conexión: El
keepAliveTimeoutde 5s expira y Node.js envía un FIN TCP. - ALB no lo sabe aún: El ALB todavía considera la conexión activa (su timeout es 60s).
- Nueva petición llega: El ALB intenta reutilizar la conexión cerrada — recibe un RST TCP.
- 502 al cliente: El ALB no puede recuperar la petición y devuelve 502.
Política IAM Mínima para Diagnóstico
Si el diagnóstico lo realiza un rol con permisos limitados, necesita al menos estas acciones para ejecutar los comandos de este artículo:
🔽 Ver política IAM mínima para diagnóstico de ALB
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"ec2:DescribeInstances",
"ec2:DescribeSecurityGroups"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-alb-logs-bucket",
"arn:aws:s3:::my-alb-logs-bucket/*"
]
}
]
}
Diagnóstico del ALB 502: Árbol de Decisión
- Si
target_status_codees-ytarget_processing_timees-1o 0: el backend no respondió — revisa keep-alive timeout y conectividad de red. - Si
target_status_codetiene un valor real pero el ALB devuelve 502: la respuesta HTTP del backend está malformada — revisa cabeceras y formato de respuesta. - Si el error es intermitente bajo baja carga: casi siempre es el keep-alive timeout desalineado.
- Si el error es en rutas específicas: revisa el tamaño de las cabeceras de respuesta y el formato del body.
Conclusión y Próximos Pasos para Resolver el 502 en ALB
Un 502 en ALB con targets healthy siempre apunta a la capa de protocolo entre el ALB y el backend — no a la disponibilidad del target. El flujo de diagnóstico es: access logs primero para clasificar el tipo de fallo, luego verificación directa contra el backend para confirmar la causa.
Los puntos de acción concretos:
- Habilita access logs del ALB si no están activos — son indispensables.
- Verifica que el keep-alive timeout del backend supere el idle timeout del ALB.
- Confirma que el protocolo del target group coincide con lo que escucha la aplicación.
- Reproduce el error directamente contra el backend desde la misma VPC.
Referencias oficiales: ALB Access Logs | Troubleshooting ALB | AWS Knowledge Center: ALB 502.
Glosario
| Término | Definición |
|---|---|
| Target Group | Agrupación lógica de backends (instancias, IPs, o funciones Lambda) hacia los que el ALB enruta tráfico. |
| Keep-Alive Timeout | Tiempo máximo que una conexión TCP persistente permanece abierta sin actividad antes de ser cerrada por uno de los extremos. |
| target_processing_time | Campo en los access logs del ALB que indica el tiempo en segundos desde que el ALB envió la petición al target hasta que recibió la primera cabecera de respuesta. Un valor de -1 indica que no se recibió respuesta. |
| Idle Timeout | Parámetro del ALB que define cuánto tiempo puede estar una conexión sin actividad antes de que el ALB la cierre. Configurable por load balancer. |
| 502 Bad Gateway | Código HTTP que el ALB devuelve al cliente cuando no puede obtener una respuesta HTTP válida del backend, independientemente de la causa. |
Comentarios
Publicar un comentario