Timeouts y errores de load balancer: 502, 503 y 504 explicados

Un 504 significa que el load balancer dejó de esperar a tu app. Un 502 significa que recibió una respuesta inválida o una conexión cortada. Compara el timeout de cada salto para encontrar al culpable.

Actualizado el 8 min de lectura

Objetivos de aprendizaje

Después de leer este artículo podrás:

  • Distinguir 502, 503 y 504 y saber qué salto los generó
  • Descubrir qué timeout (conexión, lectura, inactividad, keep-alive) alcanzó tu petición
  • Corregir la causa: handlers lentos, keep-alive desalineado, backends caídos
En esta página

Abres tu sitio y ves una página desnuda: 504 Gateway Timeout. O 502 Bad Gateway. O funciona nueve de cada diez veces y falla en la décima. Los logs de tu app no muestran nada, porque muchas veces la petición ni siquiera llegó a tu código, o llegó demasiado tarde.

Estos errores los produce lo que está delante de tu app: un load balancer, un proxy inverso o una CDN. Cada uno tiene su propio reloj, y el primero que se agota decide el error. El código de estado y el tiempo hasta el fallo te dicen qué reloj fue.

text
504 Gateway Timeout
502 Bad Gateway
503 Service Unavailable
tiempo de esperaNavegadorCDN524Load balancer504Proxy inverso504se dispara primeroApphandler lentoSin timeout: 502 respuesta inválida, 503 sin backend
Cada salto tiene su propio reloj; el primero que se dispara decide el error que llega al navegador.

¿Qué significan 502, 503 y 504?#

Los tres son errores del lado del servidor definidos en el RFC 9110, y cuando un proxy los devuelve, por lo general significan "el proxy está bien, lo que hay detrás no".

Código Significado Causa típica
502 Bad Gateway El proxy recibió una respuesta inválida del upstream La app se cayó a mitad de respuesta, conexión reiniciada, protocolo o puerto equivocado
503 Service Unavailable El servidor no puede atender ahora (sobrecarga o mantenimiento) Ningún backend sano, app sobrecargada, modo de mantenimiento deliberado
504 Gateway Timeout El proxy no recibió a tiempo la respuesta del upstream Handler lento, event loop bloqueado, consulta lenta a la base de datos

Una forma corta de recordarlo: 502 es "recibí basura o nada", 503 es "nadie puede atender esto", 504 es "esperé y nunca respondiste".

Algunas CDN usan códigos propios. Cloudflare devuelve 524 cuando se conectó a tu origen pero no recibió una respuesta HTTP a tiempo. Su timeout de lectura del proxy por defecto es de 125 segundos, y solo algunos planes permiten ampliarlo.

¿Qué timeouts hay entre el navegador y tu app?#

Una petición atraviesa varios saltos, y cada uno puede imponer un límite:

  • Timeout de conexión: cuánto espera el proxy para abrir una conexión TCP con la app. nginx usa 60 segundos por defecto (proxy_connect_timeout) y aclara que normalmente no puede superar los 75 segundos.
  • Timeout de lectura: cuánto espera el proxy entre dos lecturas de la respuesta de la app. nginx usa 60 segundos por defecto (proxy_read_timeout). Es un intervalo entre lecturas, no un límite para toda la respuesta, así que una respuesta en streaming que sigue enviando bytes no lo dispara.
  • Timeout de envío: la misma idea para escribir la petición hacia la app, 60 segundos por defecto.
  • Idle timeout: cuánto puede estar una conexión sin datos antes de que el balanceador la cierre. Un Application Load Balancer de AWS usa 60 segundos por defecto, configurable de 1 a 4000 segundos.
  • Keep-alive timeout: cuánto tiempo una conexión terminada sigue abierta para reutilizarse. Los servidores http de Node.js usan 5000 ms por defecto.

La petición legítima más lenta tiene que caber en el límite más pequeño del camino. Un reporte que tarda 90 segundos funciona sin nada delante y falla detrás de un proxy de 60 segundos.

¿Cómo saber qué salto agotó el tiempo?#

Mide desde fuera y mira el tiempo transcurrido. Un timeout es un reloj, así que el momento del fallo lo delata.

bash
curl -s -o /dev/null -w "status=%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
  https://example.com/slow-endpoint

Lee el resultado:

Falla tras Culpable probable
~60 s Valor por defecto de nginx o del ALB (timeout de lectura o idle)
~125 s Cloudflare (524 tras 125 s por defecto); otras CDN tienen sus propios límites
Al instante, o en pocos ms Conexión rechazada o reiniciada: app caída, puerto equivocado, proceso que se cayó
Aleatorio, en cualquier momento Keep-alive desalineado o un backend que a veces muere

Luego revisa el otro lado. ¿La misma URL es rápida cuando llamas a la app directamente, sin pasar por el balanceador? Si es así, la app está bien y el límite está en el camino. Si también es lenta, el problema es la app y subir los timeouts solo lo esconde. Los logs de tu app deberían mostrar la petición llegando y cuánto tardó el handler. Si la petición ni aparece en ellos, murió antes de llegar a la app.

¿Cuáles son las causas más comunes y cómo se corrigen?#

De la más común a la menos común.

1. Un handler demasiado lento#

Un reporte, una exportación, una consulta lenta o una llamada a una API externa tarda más de lo que permite el proxy. Corrígelo en este orden:

  1. Encuentra la parte lenta (plan de la consulta, llamada externa, trabajo de CPU bloqueante).
  2. Hazla rápida: agrega un índice, cachea el resultado, saca el trabajo del event loop.
  3. Si es larga por naturaleza, devuelve 202 Accepted con un ID de job al instante y deja que el cliente consulte el estado o reciba un webhook.

Subir el timeout es el último recurso, y solo mueve la pared. Si lo haces, cámbialo en todos los saltos, no solo en uno:

nginx
location / {
    proxy_pass http://app;
    proxy_connect_timeout 5s;
    proxy_read_timeout    120s;
    proxy_send_timeout    120s;
}

Un timeout de conexión corto es buena idea: si la app no acepta conexiones, falla en segundos en lugar de esperar un minuto.

2. Keep-alive desalineado (502 intermitente)#

Esta causa confunde a mucha gente porque la app está sana. El balanceador mantiene conexiones abiertas y las reutiliza. Si la app cierra primero una conexión inactiva, el balanceador puede enviar una petición por una conexión que ya está muerta y devuelve un 502. AWS lo documenta: configura el idle timeout de la aplicación mayor que el del load balancer.

Los servidores Node.js cierran las conexiones inactivas tras 5 segundos por defecto, menos que casi cualquier balanceador. Súbelo:

js
import http from "node:http";

const server = http.createServer((req, res) => {
  res.end("ok");
});

// Mayor que el idle timeout del load balancer (60s por defecto en un ALB).
server.keepAliveTimeout = 65_000;
// Mantén headersTimeout por encima de keepAliveTimeout.
server.headersTimeout = 66_000;

server.listen(process.env.PORT ?? 3000);

Con nginx delante de la app, el ajuste equivalente es la directiva keepalive_timeout dentro de upstream, que usa 60 segundos por defecto.

3. Backend caído o enfermo (503 y 502)#

Si el proceso se cayó, se quedó sin memoria o escucha en el puerto equivocado, el proxy recibe "connection refused" y responde 502 o 503. Verifica que la app escuche en 0.0.0.0 y en el puerto al que el proxy reenvía, no en 127.0.0.1 dentro de un contenedor, y lee las últimas líneas del log en busca de un crash. Los procesos terminados por falta de memoria se explican en errores de falta de memoria.

4. Event loop bloqueado#

Un runtime de un solo hilo que ejecuta un bucle síncrono largo deja de responder a todo, incluidos los health checks. El balanceador ve timeouts en todas las rutas a la vez. Lleva el trabajo pesado de CPU a un worker o a una cola.

5. Conexiones de larga duración#

Los WebSockets y los server-sent events parecen inactivos entre mensajes, así que un idle timeout los corta. Envía un ping o una línea de comentario con más frecuencia que el idle timeout más corto del camino. Ojo: un ALB no cuenta los frames PING de HTTP/2 como tráfico, así que envía datos reales. Mira cómo hospedar un servidor WebSocket.

¿Cómo prevenir estos errores?#

  • Documenta los límites. Anota el timeout de cada salto. Una práctica común es ordenarlos cliente > CDN > balanceador > proxy > app, para que cada capa interna se rinda antes que la externa y los usuarios vean tu página de error, no una genérica.
  • Mide tus endpoints. Crea alertas por la latencia p95 y p99, no por el promedio. Un p99 cerca de tu timeout es un 504 en camino.
  • Falla rápido al conectar. Unos pocos segundos bastan para una conexión local.
  • Haz asíncrono el trabajo largo, con un ID de job y un endpoint de estado.
  • Mantén varios backends. Con más de una app sana detrás de un load balancer, una caída no tumba el sitio. Reintenta solo las peticiones que se pueden repetir sin riesgo: un GET repetido es inofensivo, un POST repetido puede cobrar una tarjeta dos veces.
  • Vigila también el borde. Una CDN o un proxy inverso delante agrega su propio límite, así que prueba por el camino completo, no solo contra el origen.

Cómo hacerlo en Square Cloud#

En Square Cloud, un dominio personalizado puede servirse desde varias aplicaciones a la vez. El tráfico se reparte en round-robin con failover pasivo (no hay health check activo): si una aplicación deja de responder, las peticiones van a otra del grupo. Es el consejo de mantener varios backends, sin que tengas que operar un balanceador propio.

Haces el deploy de las aplicaciones y pones el mismo dominio personalizado en cada una. El número de aplicaciones por dominio depende del plan: hasta 2 en Standard, hasta 5 en Pro y hasta 10 en Enterprise.

Los contenedores están siempre encendidos, y el reinicio automático (activado por defecto en el deploy desde el panel) levanta de nuevo la aplicación cuando su proceso termina con error. Quedarse sin memoria detiene la aplicación hasta que subas su RAM y la inicies otra vez, así que deja margen. Los logs en tiempo real (panel, CLI, VS Code y app móvil) muestran la salida de tu proceso, errores incluidos. En Pro y Enterprise, la latencia p50/p95/p99 y los logs HTTP te muestran cuándo un endpoint se acerca a un timeout.

El proxy delante de tu app espera hasta 30 segundos para conectar con tu aplicación y no corta conexiones largas: WebSocket y SSE pasan sin buffer. El resto de la lista sigue valiendo: mantén los handlers rápidos, lleva el trabajo largo a un job y deja el keep-alive timeout de tu app por encima del idle timeout de lo que tenga delante. Mira cómo funciona en load balancing.

Red

Dominio propio, TLS automático y CDN global para tu app

Más información

Preguntas frecuentes

¿Qué diferencia hay entre 502, 503 y 504?

502 Bad Gateway: el proxy recibió una respuesta inválida del upstream, o la conexión se cortó. 503 Service Unavailable: el servidor no puede atender ahora, por sobrecarga o mantenimiento. 504 Gateway Timeout: el proxy no recibió a tiempo la respuesta del upstream.

¿Cuál es el timeout por defecto de nginx y de un Application Load Balancer de AWS?

nginx usa 60 segundos en proxy_connect_timeout, proxy_read_timeout y proxy_send_timeout. Un Application Load Balancer de AWS tiene un idle timeout de conexión de 60 segundos, configurable de 1 a 4000 segundos.

¿Por qué recibo 502 intermitentes aunque la app esté arriba?

Una causa común es que la app cierre las conexiones keep-alive inactivas antes que el load balancer. El balanceador reutiliza entonces una conexión que ya está cerrada. Haz que el keep-alive timeout de la app sea mayor que el idle timeout del balanceador.

¿Basta con subir el timeout para arreglar un 504?

Solo como parche. Una petición que tarda minutos debería convertirse en un job en segundo plano que devuelva un ID al instante, porque cada salto (navegador, CDN, balanceador, proxy) tiene su propio límite y los clientes también se rinden.

Siguiente artículoQué es un balanceador de carga y para qué sirveUn balanceador de carga reparte las solicitudes entre varios servidores. Aprende cómo funciona, los algoritmos, capa 4 y 7 y un ejemplo con nginx.

Más en Redes y dominios

Ver los 27 artículos