Intermittent 502 Bad Gateway in Docker Compose with Nginx & PHP‑FPM: how to diagnose and fix timeouts and connection overloads

By · Published on · 9 min read

Stack of cut logs piled outdoors with focus on the cut faces
Texture and repetition that work well as a hero header image Photo: Lipot Repaszky / Pexels

Intermittent 502 Bad Gateway in Docker Compose with Nginx & PHP‑FPM: how to diagnose and fix timeouts and connection overloads. A 502 happens when Nginx fails to talk to PHP‑FPM or when the backend closes the connection early. Find quick checks, timeout adjustments, FPM and system limits to stabilize the stack without hiding slowdowns.

Quick diagnosis: what to check first

Confirm where the 502 happens and collect evidence. Correlate Nginx errors with signs of PHP‑FPM saturation and system limits. Reproduce with a light endpoint and a heavy one to compare.

If the intermittent 502 in Docker Compose with Nginx and PHP‑FPM only appears under load, check container restarts and the Docker bridge network. Gathering logs close to the event speeds root-cause analysis.

  • Nginx error log: messages mentioning upstream, connect() and timed out.
  • PHP‑FPM logs: slowlog, pm.max_children reached, stuck workers.
  • CPU/RAM usage and FPM process count during the spike.
  • Container restarts near the incident.
# 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

What causes intermittent 502 in this stack?

The most common causes mix timeouts and saturation. Nginx returns 502 when it can't communicate with the upstream (PHP‑FPM) or when the upstream closes the connection prematurely. The table below maps symptoms to likely causes and where to adjust.

Network drops and DNS issues in Docker can also cause intermittency. If the bridge loses packets, Nginx may fail to connect to FPM. See also why containers lose connection in production: Docker bridge drops connections.

Log symptom Likely cause Where to adjust
upstream timed out (110) while reading response header Slow response; timeout set too low Nginx: fastcgi_read_timeout; App: endpoint optimization
connect() failed (111: Connection refused) to upstream FPM crashed/restarted; backlog maxed out FPM: listen.backlog; Docker: healthchecks/restart; system: somaxconn
upstream prematurely closed connection FPM worker killed the request FPM: request_terminate_timeout; App: internal timeouts
no such file or directory while connecting to upstream Unix socket missing or permission issue FPM: listen=; permissions; socket volume
Too many open files Low file descriptor limit ulimit nofile; Nginx/FPM worker_rlimit_nofile

How to diagnose timeouts between Nginx and PHP‑FPM

Read Nginx's error.log and raise timeouts carefully. If the 502 disappears after increasing fastcgi_read_timeout, the root cause is slow processing or backend blocking. Raising timeouts only masks slowness; fix the root cause afterwards.

Also tune fastcgi_connect_timeout and keep the connection with fastcgi_keep_conn on to reduce handshake overhead.

  • Look for upstream timed out, prematurely closed connection and connect() failed.
  • Test a light endpoint (/health) and a heavy endpoint to compare.
  • Avoid mismatched timeouts between Nginx and 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;
  }
}

How many connections can PHP‑FPM actually handle?

The effective limit is pm.max_children. Each child consumes memory; calculate based on the worst-case per-worker RSS. pm.max_requests helps recycle workers that grow too large. Increasing pm.max_children without available memory causes OOM and restarts.

See the FPM documentation for pm.* and timeout details. For advanced sizing, check our FPM tuning guide: VPS sizing & PHP‑FPM tuning for seasonal peaks.

  • Measure RSS per worker: peak memory of a child times max_children.
  • Prefer pm=dynamic for variable traffic; use ondemand for low‑activity environments.
  • Enable status and slowlog to expose bottlenecks.
; 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

When to use Unix socket vs TCP for the upstream

Unix sockets reduce overhead and latency on the same host, but require a shared volume and correct permissions. TCP is simpler between containers and is easier to debug with ss and tcpdump.

In a Compose setup on a single host, Unix sockets typically perform better. Use TCP across multiple hosts.

  • Unix: lower latency; watch listen.backlog and UID/GID permissions.
  • TCP: easier to debug; check MTU and the Docker bridge.
  • Do not expose port 9000 publicly; keep the network internal to 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;
}

Connection overloads and connect() errors: how to check

connect() errors point to FPM being unavailable, a full backlog, or file-descriptor exhaustion. Validate the socket queue, somaxconn and nofile. Too-low limits cause 502 under spikes.

Tune ulimits in Compose and confirm the kernel accepts queues larger than your listen.backlog.

  • Compare listen.backlog with net.core.somaxconn.
  • Check nofile for Nginx and FPM.
  • Monitor active connections on :9000 or on the 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'](/too-many-open-files-linux-php-fpm)

Health checks and Docker Compose limits to avoid downtime

Healthchecks reduce 502 windows by exposing only healthy instances. Combine them with restart policies and a stop_grace_period so workers shut down gracefully. Tune ulimits to handle connection spikes.

For application readiness, point the healthcheck at a light endpoint. In PHP stacks, publishing /health helps validate external dependencies.

  • Simple healthcheck for php-fpm or Nginx.
  • stop_grace_period long enough for long requests.
  • ulimits nofile consistent with expected traffic.
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: fixes in the right order

Prioritize validating the cause before raising limits. The sequence below avoids blind changes and reduces the risk of masking application issues.

For operational metrics and FPM alerts in Compose, follow the PHP‑FPM metrics tutorial: expose PHP‑FPM metrics.

  • 1) Reproduce the 502 and collect Nginx/FPM logs near the event.
  • 2) Confirm it's a timeout: incrementally adjust fastcgi_connect/read_timeout.
  • 3) Check pm.max_children, slowlog and memory per worker; tune the pool.
  • 4) Fix nofile, listen.backlog and somaxconn if you see backlog/connection errors.
  • 5) Optimize slow endpoints; only then increase timeouts.
  • 6) Add healthchecks and graceful restarts to reduce failure windows.
  • 7) Revalidate with a controlled load test and monitor. In intermittent 502 cases on Docker Compose with Nginx and PHP‑FPM, measuring and tuning with data prevents regressions.

Frequently asked questions

How can I tell if the 502 comes from a timeout or from PHP‑FPM crashing?

Read Nginx's error.log: upstream timed out indicates a timeout; connect() failed/refused indicates FPM crash or a full backlog. In FPM, check pm.max_children, OOM events and restarts. If increasing fastcgi_read_timeout temporarily “fixes” it, the root cause is backend slowness.

Which timeout should I adjust first to reduce 502s?

Tune fastcgi_connect_timeout for quick connection attempts (2–5s) and then increase fastcgi_read_timeout incrementally (20–30s) while you fix slow endpoints. Avoid excessive values that hide problems and lengthen queues in Nginx and FPM.

Will increasing pm.max_children always fix 502 Bad Gateway?

No. Raising pm.max_children without sufficient memory causes OOMs and restarts, making 502s worse. Measure per‑worker memory, use pm.max_requests, and optimize endpoints with the slowlog before increasing max_children.

How can I view the FPM queue and processes in real time?

Enable pm.status_path and query /fpm-status to see process states. Monitor connections with ss/netstat and compare listen.backlog with somaxconn. Exporting metrics to Prometheus helps detect saturation early.

Is it worth switching from TCP to Unix socket for more stability?

On a single host, Unix sockets reduce overhead and can stabilize latency. Across hosts or when you need network observability, TCP is easier. Test both under load and keep listen.backlog and permissions correct.

Do Compose healthchecks prevent 502s during deploys?

Yes. Healthchecks expose only healthy instances and, combined with an appropriate stop_grace_period, avoid killing in‑flight requests. Pair them with proper nofile limits and monitoring to shrink 502 windows during rollouts.

Want to stabilize Nginx + PHP‑FPM in Docker Compose?

If you're facing intermittent 502s, we can review logs, critical settings and architecture to find the root cause and apply safe fixes.

Talk about my case

See also