The nginx error connect() to unix:/run/php/php-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream looks like a connection problem. It is not. Errno 11 is EAGAIN. nginx uses non-blocking sockets, and in this context the kernel’s accept queue for the PHP-FPM Unix socket is full. The kernel had nowhere to buffer the new FastCGI connection, so it refused the connect(2) immediately.

This is a saturation error, not a connection error. PHP-FPM is alive, the socket file exists, the master is listening, and systemctl status php-fpm will look healthy. The pool is out of workers, and the backlog buffer between nginx and PHP-FPM has filled.

Do not raise listen.backlog on its own. A larger backlog only lengthens the queue of requests that will eventually time out. The real fixes are the standard pool-exhaustion remedies: add workers if RAM permits, make requests faster, or shed traffic. Backlog tuning buys seconds of buffer at best; it does not add throughput.

What this means

PHP-FPM listens on a Unix domain socket (or a TCP socket). Between nginx and PHP-FPM’s workers sits a kernel-managed accept queue, sized by listen.backlog in the pool config and clamped to min(listen.backlog, net.core.somaxconn).

When nginx opens a FastCGI connection, the kernel places it in that accept queue. The PHP-FPM master pulls connections off the queue and hands them to idle workers. Each worker handles exactly one request at a time. Maximum concurrent request capacity equals the number of active workers, bounded by pm.max_children.

When every worker is busy, the queue fills. When the queue reaches its maximum, the kernel refuses new connect(2) calls from non-blocking sockets with EAGAIN (errno 11). nginx logs the connect failure and returns 502 Bad Gateway.

flowchart LR
  A[nginx] -->|connect| B[kernel accept queue]
  B -->|accept| C[FPM master]
  C -->|dispatch| D[worker pool]
  D -->|all busy| E[max_children reached]
  E -->|queue fills| F[connect EAGAIN]
  F -->|502| G[user]

This is distinct from errno 111 (Connection refused), which means nothing is listening on the socket. With EAGAIN, something is listening, but the queue is full. The PHP-FPM master is up, the socket file exists, and the process looks healthy. Only the kernel and nginx know the queue is saturated.

Two gotchas around the backlog value itself:

  • On Linux, setting listen.backlog = -1 in the pool config (the PHP manual says this means “unlimited” on BSD) is silently treated as too low and replaced with 128. PHP-FPM logs a warning such as listen.backlog(-1) was too low for the ondemand process manager. I updated it for you to 128. The documented “-1 means maximum” behavior applies to BSD, not Linux.
  • The effective backlog on Linux is min(listen.backlog, net.core.somaxconn). Even an explicit large value can be capped by the kernel default. Check cat /proc/sys/net/core/somaxconn; the default varies by distribution (128 on older distributions, 4096 on modern kernels).

The PHP-FPM default for listen.backlog changed across versions: 511 on Linux for PHP releases before 8.2, and -1 (clamped to somaxconn) on PHP 8.2 and later. Either way, the kernel has the final say on the effective size.

Common causes

CauseWhat it looks likeFirst thing to check
All workers busy on a slow backendactive processes pinned at max_children, listen queue growing, CPU lowSlow log for stack traces blocked on DB or external API
Traffic burst beyond pm.max_childrenRapid climb in accepted connection rate, idle processes hits zeroAccepted conn rate against baseline
Effective backlog clamped lowss -xlnp Send-Q is 128 or 511 while workers still have headroomnet.core.somaxconn and listen.backlog in pool config
ondemand cold-start lagSpike after idle period, workers spawning slowlypm mode and pm.process_idle_timeout
DDoS or retry stormMany connections from a small set of IPs, error rate spikesnginx access log for IP concentration

Quick checks

# Socket exists and master is listening. For the listen socket:
#   Send-Q = effective backlog capacity (after somaxconn clamp)
#   Recv-Q = current queued connections (near Send-Q means queue is full)
ss -xlnp | grep php

# PHP-FPM status page: current queue depth, peak since start, configured len
# Path depends on pm.status_path in pool config
curl -s http://127.0.0.1/fpm-status | grep "listen queue"

# Confirm workers are at the ceiling
curl -s http://127.0.0.1/fpm-status | grep -E "^(active|idle|total) processes"

# Counter of times pm.max_children blocked a spawn (dynamic and ondemand only)
curl -s http://127.0.0.1/fpm-status | grep "^max children reached"

# nginx EAGAIN errors scoped to the current hour
grep "$(date '+%Y/%m/%d %H')" /var/log/nginx/error.log | grep -c "Resource temporarily unavailable"

# Kernel-level accept queue overflows and drops (cumulative counters)
nstat | grep -iE "ListenOverflows|ListenDrops"

# Effective somaxconn clamp
cat /proc/sys/net/core/somaxconn

# Per-process FD limit for the master (workers hitting EMFILE will fail
# to open database/API connections, producing slow requests)
cat /proc/$(pgrep -f 'php-fpm: master' | head -1)/limits | grep "Max open files"

All of these are read-only and safe to run during an incident.

How to diagnose it

  1. Confirm EAGAIN, not Connection refused. The nginx error string contains (11: Resource temporarily unavailable). Errno 111 would read (111: Connection refused) and points to a different failure path: no listener, dead master, wrong socket path. See the related guide on connection refused for that.

  2. Inspect the listen socket with ss -xlnp. The Send-Q column shows the kernel’s actual backlog capacity after the somaxconn clamp. The Recv-Q column shows the current number of connections waiting in the accept queue. When Recv-Q sits at or near Send-Q, the queue is full and the kernel is refusing new connects.

  3. Pull the PHP-FPM status page. The three fields that matter for this symptom are listen queue (current snapshot), max listen queue (peak since master start), and listen queue len (the backlog PHP-FPM passed to listen()). A non-zero max listen queue means the pool has experienced queuing even if the current value is zero.

  4. Check active processes against pm.max_children. If active is pinned at max_children, the pool is exhausted. If idle processes is zero in dynamic or static mode, you are one burst away from EAGAIN.

  5. Look at max children reached. If the counter is incrementing, the process manager wanted to spawn more workers but hit the ceiling. This is the smoking gun for pool exhaustion, and it is only meaningful for dynamic and ondemand modes (always zero for static).

  6. Read the slow log. If request_slowlog_timeout is configured (it defaults to 0, meaning disabled), recent entries show which script and which call stack is holding workers. The slow log is what tells you whether this is a traffic problem or a slow-backend problem.

  7. Cross-check the kernel drop counters. nstat | grep -iE 'ListenOverflows|ListenDrops' shows connections the kernel refused because the accept queue was full. These never appear in the PHP-FPM status page. PHP-FPM has no visibility into kernel-level drops.

  8. Inspect per-worker state. The full status page (/fpm-status?full) shows each worker’s current request URI, request duration, and last request cpu. Workers stuck on the same URI for seconds are typically blocked on a backend dependency. Workers showing high CPU are compute-bound.

Metrics and signals to monitor

SignalWhy it mattersWarning sign
listen queue (status page)Earliest direct signal that workers cannot keep upAny sustained non-zero value
max listen queueHigh-water mark since master start; catches spikes between pollsApproaches listen queue len
Recv-Q on the listen socketKernel-level confirmation of queue depthNear Send-Q
active processes / max_childrenPool utilization ratioSustained above 0.8
idle processesBurst headroomZero in dynamic or static mode
max children reached rateTimes the PM wanted more workers but could not spawnIncrementing during normal traffic
slow requests rateWorkers stuck past request_slowlog_timeoutAny non-zero rate
ListenOverflows / ListenDropsConnections the kernel refused at the socketNon-zero and increasing
nginx 502 rateUser-visible impactAny non-zero during real traffic

Fixes

Add workers, if RAM permits

The direct fix. Raise pm.max_children in the pool config and reload:

systemctl reload php-fpm
# or: kill -USR2 $(pgrep -f 'php-fpm: master')

Before doing this, verify memory headroom. A common sizing heuristic: memory-safe max_children = (total_RAM * 0.7 - OS_overhead) / avg_worker_RSS. Use PSS via smem or /proc/<pid>/smaps_rollup for the per-worker number, not raw RSS, because RSS double-counts the opcache shared segment across forked workers and overstates unique memory by roughly 30 to 50 percent.

The reload itself has a brief no-worker window. SIGUSR2 drains existing workers before the master re-execs, so expect a short spike in EAGAIN errors during the reload.

Reduce request duration

If the slow log points to a backend (database, external API, NFS, Redis), the bottleneck is not PHP-FPM. Adding workers only buys time before the next saturation event. Fix the slow query, tighten the cURL timeout, add a circuit breaker, or shed the slow endpoint at the nginx layer.

A single request that takes 5 seconds instead of 50ms holds a worker 100x longer. A handful of slow endpoints can quietly consume most of the pool while aggregate traffic looks normal.

Shed traffic at the web server

If this is a burst, DDoS, or retry storm, the right response is rate limiting or IP blocking at nginx, not bigger pools. Look at the access log for IP concentration, request URI patterns, and user-agent strings. The pool will refill once traffic subsides.

Do not just raise listen.backlog

The backlog is a buffer, not throughput. A larger backlog lets more connections pile up waiting, but they still need a worker eventually. If the pool stays saturated for seconds, a bigger backlog just means more requests that hit nginx’s fastcgi_read_timeout and return 504 instead of 502. You have moved the failure mode, not fixed it.

The default listen.backlog (511 on Linux for PHP releases before 8.2, -1 clamped to somaxconn on PHP 8.2+) is sufficient for almost every workload. The one reasonable kernel-side change is raising net.core.somaxconn if it is still at the legacy 128 default on an older distribution. Treat that as a small buffer improvement, not a saturation fix.

ondemand cold-start

If pm = ondemand, idle workers are killed after pm.process_idle_timeout (default 10s). A burst after idle must wait for fork plus PHP initialization before workers can accept. The error log may show listen.backlog(-1) was too low for the ondemand process manager as a secondary symptom. Consider switching to dynamic or static for predictable headroom, or accept the cold-start latency as the cost of lower idle memory.

Prevention

  • Monitor the listen queue field on the status page. It gives minutes of advance warning before the kernel starts dropping connections. By the time nginx logs EAGAIN, you are already in the failure state.
  • Set pm.max_requests to 500-1000. Without worker recycling, memory leaks accumulate and eventually trigger OOM kills that look like saturation.
  • Enable request_slowlog_timeout (for example 5 seconds). It is the single signal that tells you why workers are busy, not just that they are. It defaults to 0 (disabled) in many distributions.
  • Track active processes / max_children as a leading indicator. Capacity target: peak utilization at or below 75% of max_children.
  • Verify net.core.somaxconn is not still at the legacy 128 default if your distribution ships that. A modern default removes one rare clamp.
  • If your pool config sets listen.backlog = -1, confirm what PHP-FPM actually applied on Linux. -1 is not unlimited on Linux; it is silently replaced.
  • Poll the status page at 1-second intervals for operational alerting. A 10 to 30 second interval will miss transient saturation events.

How Netdata helps

  • Per-second collection of PHP-FPM status page fields (active processes, idle processes, listen queue, max children reached, listen queue len) makes the saturation form visible in real time, not between 10-second polls that miss transient queue build-ups.
  • Anomaly ML on active processes and listen queue catches the climb before EAGAIN reaches nginx, without requiring static thresholds that differ per workload.
  • Same-timeline correlation of nginx 502/504 error rate, PHP-FPM pool saturation, and kernel ListenOverflows narrows the cause from a single dashboard view rather than across three tools.
  • Per-pool dashboards for multi-pool deployments. A single saturated pool behind a round-robin load balancer shows up as every-Nth-request slowness unless each pool is tracked independently.
  • Container cgroup-level memory tracking catches the OOM-kill variant of this symptom before PHP-FPM’s own logs surface it, which matters in container deployments where the master can be killed without a syslog entry.