502 Bad Gateway intermitente en Docker Compose con Nginx y PHP‑FPM: cómo diagnosticar y corregir timeouts y exceso de conexiones

Por · Publicado el · 9 min de lectura

Pila de troncos cortados apilados al aire libre con enfoque en las caras cortadas
Textura y repetición que funcionan bien como imagen destacada en encabezados Foto: Lipot Repaszky / Pexels

502 Bad Gateway intermitente en Docker Compose con Nginx y PHP‑FPM: cómo diagnosticar y corregir timeouts y exceso de conexiones. El 502 aparece cuando Nginx no logra comunicarse con PHP‑FPM o cuando el backend cierra la conexión antes de tiempo. Revisa comprobaciones rápidas, ajustes de timeouts, parámetros de FPM y límites del sistema para estabilizar la pila sin enmascarar latencia.

Diagnóstico rápido: qué verificar primero

Confirma dónde ocurre el 502 y recopila evidencias. Cruza errores de Nginx con señales de saturación en PHP‑FPM y límites del sistema. Reproduce el fallo con un endpoint ligero y otro pesado para comparar.

Si el 502 Bad Gateway intermitente en Docker Compose con Nginx y PHP‑FPM solo aparece bajo pico, revisa reinicios de contenedores y la red bridge. Recolectar logs cercanos al evento acelera la identificación de la causa raíz.

  • Logs de error de Nginx: mensajes con upstream, connect() y timed out.
  • Logs de PHP‑FPM: slowlog, max_children alcanzado, workers bloqueados.
  • Uso de CPU/RAM y conteo de procesos del FPM durante el pico.
  • Reinicios de contenedores cerca del incidente.
# 1) Estado rápido dos serviços
docker compose ps

# 2) Últimos erros do Nginx (procure upstream/timeout)
docker compose logs -f nginx | sed -n '1,200p'
# Opcional: dentro do container, filtrar erros comuns
# docker compose exec nginx sh -lc 'grep -E "upstream|connect\(\)|timed out" -n /var/log/nginx/error.log | tail -n 50'

# 3) Erros do PHP-FPM (stdout/err costuma concentrar)
docker compose logs -f php-fpm | sed -n '1,200p'

# 4) Teste simples de latência/HTTP code (ajuste URL)
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" https://seu-dominio/health

¿Qué causa un 502 Bad Gateway intermitente en esta pila?

Las causas más comunes combinan timeout y saturación. Nginx devuelve 502 cuando no puede comunicarse con el upstream (PHP‑FPM) o recibe un cierre prematuro de la conexión. La tabla siguiente mapea síntomas a la probable causa y dónde ajustar.

Caídas de red y DNS en Docker también generan intermitencia. Si el bridge pierde paquetes, Nginx puede no conectar con el FPM. Mira también por qué los contenedores pierden conexión en producción: conexiones en el bridge de Docker.

Síntoma en el log Causa probable Dónde ajustar
upstream timed out (110) while reading response header Respuesta lenta; timeout bajo Nginx: fastcgi_read_timeout; App: optimización del endpoint
connect() failed (111: Connection refused) to upstream FPM cayó/reinició; backlog saturado FPM: listen.backlog; Docker: health/restart; sistema: somaxconn
upstream prematurely closed connection Worker del FPM mató la petición FPM: request_terminate_timeout; app: timeouts internos
no such file or directory while connecting to upstream Socket Unix inexistente/permiso FPM: listen=; permisos; volumen del socket
Too many open files Límite de descriptores bajo ulimit nofile; Nginx/FPM worker_rlimit_nofile

Cómo diagnosticar timeouts entre Nginx y PHP‑FPM

Lee el error.log de Nginx y aumenta los timeouts con criterio. Si el 502 desaparece al elevar fastcgi_read_timeout, la causa es procesamiento lento o bloqueos en el backend. Aumentar el timeout enmascara la lentitud; corrige la causa raíz después.

Ajusta también fastcgi_connect_timeout y mantén la conexión con fastcgi_keep_conn on para reducir el overhead de handshakes.

  • Busca upstream timed out, prematurely closed connection y connect() failed.
  • Prueba un endpoint ligero (/health) y uno pesado para comparar.
  • Evita timeouts desbalanceados entre Nginx y FPM.
# Exemplo de server com timeouts explícitos
server {
  listen 80;
  server_name exemplo.local;

  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    # Upstream para o FPM no Compose (serviço "php-fpm")
    fastcgi_pass php-fpm:9000; # ou unix:/var/run/php/php-fpm.sock

    # Timeouts: comece conservador e valide com carga
    fastcgi_connect_timeout 3s;
    fastcgi_send_timeout 30s;
    fastcgi_read_timeout 30s;
    fastcgi_keep_conn on;
  }
}

¿Cuántas conexiones aguanta realmente PHP‑FPM?

El límite efectivo es pm.max_children. Cada child consume memoria; calcula en base al pico máximo. pm.max_requests ayuda a reciclar workers que crecen demasiado. Aumentar pm.max_children sin memoria disponible provoca OOM y reinicios.

Consulta la documentación de FPM para detalles de pm.* y timeouts. Para dimensionamiento avanzado, mira nuestra guía de tuning de FPM: dimensionamiento y tuning para picos.

  • Mide el RSS por worker: pico de memoria del child multiplicado por max_children.
  • Prefiere pm=dynamic para tráfico variable; ondemand en entornos poco cargados.
  • Activa status y slowlog para evidenciar cuellos de botella.
; pool.d/www.conf — exemplo base (ajuste ao seu tráfego)
pm = dynamic
pm.max_children = 24
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500

; Tempo-limite duro por request (protege contra travas)
request_terminate_timeout = 30s

; Backlog do socket (fila de conexões pendentes)
listen.backlog = 1024

; Observabilidade
pm.status_path = /fpm-status
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s

Cuándo usar socket Unix vs TCP para el upstream

Los sockets Unix reducen overhead y latencia en el mismo host, pero requieren volumen compartido y permisos correctos. TCP es más simple entre contenedores y facilita el debug con ss y tcpdump.

En Compose en el mismo host, los sockets suelen rendir mejor. En múltiples hosts, usa TCP.

  • Unix: menor latencia; atención a listen.backlog y permisos UID/GID.
  • TCP: debug sencillo; respeta MTU y revisa la bridge de Docker.
  • No expongas el puerto 9000 públicamente; usa la red interna del Compose.
# docker-compose.yml — exemplo com socket Unix
services:
  php-fpm:
    image: php:fpm
    volumes:
      - php-sock:/var/run/php
      - ./:/var/www/html
  nginx:
    image: nginx:alpine
    volumes:
      - php-sock:/var/run/php
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php-fpm
volumes:
  php-sock:

# www.conf (FPM)
; listen para socket Unix compartilhado
listen = /var/run/php/php-fpm.sock
listen.mode = 0660
listen.group = www-data

# nginx.conf (trecho)
location ~ \.php$ {
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_pass unix:/var/run/php/php-fpm.sock;
  fastcgi_read_timeout 30s;
}

Exceso de conexiones y errores de connect(): cómo comprobar

Los errores de connect() apuntan a FPM indisponible, backlog saturado o agotamiento de descriptores. Valida la cola del socket, somaxconn y nofile. Límites demasiado bajos generan 502 bajo picos.

Ajusta ulimits en Compose y verifica que el kernel acepte colas mayores que tu listen.backlog.

  • Compara listen.backlog con net.core.somaxconn.
  • Revisa nofile para Nginx y FPM.
  • Monitorea conexiones activas en :9000 o en el socket.
# Conexões/escuta do FPM (TCP)
ss -ltnp | grep :9000 || true

# Backlog máximo do kernel (precisa ser >= listen.backlog)
cat /proc/sys/net/core/somaxconn

# Limite de arquivos por processo (dentro do container)
ulimit -n

# Procure indicadores no config do FPM
# docker compose exec php-fpm sh -lc "grep -E 'pm\.max_children|listen\.backlog' -n /usr/local/etc/php-fpm.d/*.conf"

# Se ver 'Too many open files', ajuste também:
# - ulimits no Compose para Nginx e FPM
# - worker_rlimit_nofile (Nginx)
# Guia correlato: [resolver 'Too many open files'](/es/too-many-open-files-php-fpm)

Healthchecks y límites en Docker Compose para evitar caídas

Los healthchecks reducen ventanas de 502 exponiendo solo instancias saludables. Combínalos con políticas de reinicio y stop_grace_period para terminar workers con dignidad. Ajusta ulimits para soportar picos de conexiones.

Para readiness de la aplicación, apunta el healthcheck a un endpoint ligero. En stacks PHP, publicar /health ayuda a validar dependencias externas.

  • Healthcheck simple en php-fpm o en Nginx.
  • stop_grace_period suficiente para requests largas.
  • ulimits nofile coherente con el tráfico.
services:
  php-fpm:
    image: php:fpm
    healthcheck:
      test: ["CMD-SHELL", "php-fpm -t >/dev/null 2>&1 || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 3
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
    stop_grace_period: 30s

  nginx:
    image: nginx:alpine
    depends_on:
      - php-fpm
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
    restart: unless-stopped

Checklist de correcciones en el orden correcto

Prioriza la validación de la causa antes de aumentar límites. La secuencia abajo evita ajustes a ciegas y reduce el riesgo de enmascarar problemas de aplicación.

Para métricas operativas y alertas del FPM en Compose, sigue el tutorial para exponer métricas: exponer métricas del PHP‑FPM.

  • 1) Reproduce el 502 y recopila logs Nginx/FPM cercanos al evento.
  • 2) Confirma si es timeout: ajusta fastcgi_connect/read_timeout de forma incremental.
  • 3) Verifica pm.max_children, slowlog y memoria por worker; ajusta el pool.
  • 4) Corrige nofile, listen.backlog y somaxconn si hay colas/errores de connect().
  • 5) Optimiza endpoints lentos; solo entonces mantén timeouts mayores.
  • 6) Añade healthchecks y restart graceful para reducir ventanas de fallo.
  • 7) Revalida con pruebas de carga controladas y monitoriza. En 502 Bad Gateway intermitente en Docker Compose con Nginx y PHP‑FPM, medir y ajustar con datos evita recaídas.

Preguntas frecuentes

Cómo saber si el 502 viene de timeout o de caída del PHP‑FPM?

Lee el error.log de Nginx: upstream timed out indica timeout; connect() failed/refused indica caída del FPM o backlog lleno. En el FPM, revisa max_children alcanzado, OOM y reinicios. Si aumentar fastcgi_read_timeout “resuelve” temporalmente, la causa es lentitud del backend.

Qué timeout ajustar primero para reducir 502?

Ajusta fastcgi_connect_timeout para conexiones rápidas (2–5s) y fastcgi_read_timeout de forma incremental (20–30s) mientras corriges endpoints lentos. Evita valores excesivos, porque enmascaran problemas y aumentan las colas en Nginx y FPM.

Aumentar pm.max_children siempre resuelve 502 Bad Gateway?

No. Aumentar pm.max_children sin memoria suficiente provoca OOM y reinicios, empeorando el 502. Antes, mide el consumo por worker y ajusta pm.max_requests. Optimiza endpoints con slowlog. Solo aumenta max_children si hay capacidad real.

Cómo ver la cola del PHP‑FPM y procesos en tiempo real?

Activa pm.status_path y consulta /fpm-status para ver procesos y estados. Monitorea conexiones con ss/netstat y la cola del socket (listen.backlog vs somaxconn). Las métricas en Prometheus ayudan a detectar saturación anticipada.

Vale la pena migrar de TCP a socket Unix para ganar estabilidad?

En un único host, los sockets Unix reducen overhead y pueden estabilizar latencias. En múltiples hosts o cuando necesitas observabilidad de red, TCP facilita las herramientas de debug. Prueba ambos bajo carga, manteniendo listen.backlog y permisos correctos.

Los healthchecks en Compose evitan 502 durante deploy?

Sí. Los healthchecks exponen solo instancias saludables y, con stop_grace_period adecuado, evitan terminar peticiones en curso. Combínalos con límites nofile y monitorización para reducir ventanas de 502 durante cambios de versión.

¿Quieres estabilizar Nginx + PHP‑FPM en Docker Compose?

Si sufres 502 intermitente, podemos revisar logs, parámetros críticos y arquitectura para encontrar la causa raíz y aplicar correcciones seguras.

Hablar sobre mi caso

Ver también