When PHP-FPM workers pile up, the status page tells you they are busy. It does not tell you why. Active processes climb toward pm.max_children, the listen queue fills, and you end up correlating timestamps against database slow query logs, APM traces, and application error logs to reconstruct what happened. The slow log closes that gap. When a request exceeds request_slowlog_timeout, the FPM master ptraces the worker and writes a backtrace naming the exact script and call that blocked.

It ships disabled. request_slowlog_timeout defaults to 0: no slow log is ever written. This is the most common monitoring gap in PHP-FPM deployments. Teams know workers are busy but not what they are busy on. This guide covers enabling the slow log, tuning it, verifying it end to end, and avoiding the platform-specific traps (Docker capabilities, SELinux, user isolation) that leave the file empty even after you think you have turned it on.

Pick a value below your user-visible latency budget and below request_terminate_timeout. Five seconds is a reasonable starting point for most web workloads. Enable it on every production pool and stop guessing.

What this captures

When a worker exceeds request_slowlog_timeout, the master captures a snapshot of where the worker was executing:

sequenceDiagram
    participant W as Worker
    participant M as FPM master
    participant L as slowlog file
    Note over W: request exceeds request_slowlog_timeout
    M->>W: SIGSTOP
    M->>W: ptrace ATTACH + read stack frames
    M->>L: write timestamp, PID, script, line, backtrace
    M->>W: SIGCONT

The mechanism is ptrace-based. The master sends SIGSTOP to the worker, attaches via ptrace, reads the call stack out of the worker’s address space, writes the trace to the slowlog path, then sends SIGCONT to resume execution. This briefly pauses the already-slow request. The cost is negligible next to the diagnostic value: you learn not just that a request was slow but the function and file where it was caught.

Each slow log entry contains:

  • a timestamp and pool name
  • the worker PID
  • the script filename and line number where execution was caught
  • a backtrace of stack frames (depth governed by request_slowlog_trace_depth, default 20, available since PHP 7.2.0)
  • the request URI

The status page also exposes a cumulative slow requests counter. The counter is what you alert on. The file is what you read when the counter moves.

One caveat from the mechanism: writing the trace is a point-in-time snapshot. The worker may have spent most of its time blocked deeper in the call stack than where it was caught. Multiple entries for the same endpoint over time build a more reliable picture than any single trace.

Prerequisites

  • Pool configuration access: write access to the pool config, typically /etc/php/<version>/fpm/pool.d/www.conf.
  • Reload authority: enabling the slow log requires a graceful reload (SIGUSR2) or full restart of PHP-FPM.
  • A writable slow log path: the master must be able to create and append to the slowlog file. The directory must be owned by the master process user and not world-writable.
  • ptrace permission: the master must be allowed to ptrace the worker. This is the most common failure mode. See “Common pitfalls”.

Procedure

  1. Open the pool configuration for the pool you want to instrument.

    # Locate the pool config for your PHP version
    ls /etc/php/*/fpm/pool.d/
    
  2. Set the timeout. Add or uncomment request_slowlog_timeout. Bare integers are interpreted as seconds; s, m, h, d suffixes are also accepted.

    request_slowlog_timeout = 5
    

    Five seconds is a reasonable starting point for most web workloads. Lower it to 1 or 2 seconds to catch latency regressions early. Raise it to 10 or 30 seconds if your application has legitimately long endpoints and the log is too noisy.

  3. Set the slow log path. The default varies by distribution and install prefix. Set it explicitly so you know where to look.

    slowlog = /var/log/php-fpm/www-slow.log
    
  4. Optionally tune trace depth. The default of 20 frames is usually enough. Raise it if you run a deep framework stack and the bottom of the trace is truncated.

    request_slowlog_trace_depth = 20
    

    This directive is available since PHP 7.2.0. On earlier versions the depth is not configurable.

  5. If the pool runs as a different user than the master, enable dumpable processes. When workers run as www-data but the master runs as root (the common case), ptrace from the master to the worker is blocked unless process.dumpable is set to yes. The directive defaults to no.

    process.dumpable = yes
    
  6. Create the log directory and fix ownership.

    # Create the directory owned by the master user (often root)
    mkdir -p /var/log/php-fpm
    chown root:root /var/log/php-fpm
    chmod 0755 /var/log/php-fpm
    
  7. Validate the configuration before reloading.

    # Test config syntax without applying it
    php-fpm -tt 2>&1 | grep -E 'slowlog|request_slowlog_timeout|process.dumpable'
    

    php-fpm -tt tests the configuration and prints the resolved values. Do not skip this. A syntax error in the pool config will prevent the reload from completing and leave the master running the old config, or fail to start after a full restart.

  8. Reload PHP-FPM.

    # Graceful reload (SIGUSR2)
    systemctl reload php-fpm
    

    Schedule the reload during low traffic. SIGUSR2 is the documented graceful reload signal, but treat any FPM reload as potentially disruptive until you have observed behavior under your specific PHP version and traffic pattern.

Verifying it works

Do not assume the slow log works because the config reloaded. The ptrace path fails silently in several common environments. Verify end to end.

  1. Confirm the resolved config.

    # Verify the master actually parsed the directives
    php-fpm -tt 2>&1 | grep -E 'slowlog|request_slowlog_timeout'
    

    You should see your timeout as a non-zero value and your slow log path.

  2. Trigger a deliberately slow request. Drop a temporary test script that sleeps longer than your timeout. Remove it when done.

    <?php sleep(10); ?>
    

    Request it through the web server, or hit it directly via FastCGI.

  3. Check the slow log file.

    tail -50 /var/log/php-fpm/www-slow.log
    

    You should see an entry with the test script’s filename, the sleep() line, and a backtrace. If the file is empty but the status counter incremented, the master believed it logged but the write went to a stale file descriptor (see the log rotation pitfall) or the worker was killed before the trace finished.

  4. Check the slow requests counter.

    curl -s http://127.0.0.1/fpm-status | grep "slow requests"
    

    The counter should increment once per slow request. It is cumulative since pool start, so compare deltas, not absolute values.

  5. If the file is empty and the counter did not move, work through “Common pitfalls”. The most likely cause on containerized or SELinux-enforced hosts is a denied ptrace.

Common pitfalls

Empty slow log with no errors (Docker). Containers drop CAP_SYS_PTRACE by default. Without it, ptrace(ATTACH) fails with “Operation not permitted” and the slow log stays empty even though the master logs a WARNING that it is logging the request. Add the capability to the container:

docker run --cap-add=SYS_PTRACE ...

For Kubernetes, add SYS_PTRACE to the container’s security context capabilities. This is the single most reported slow-log issue in containerized deployments.

Empty slow log with no errors (SELinux). On RHEL and CentOS 7+, SELinux blocks ptrace even when process.dumpable = yes is set. The error in the FPM log is failed to ptrace(ATTACH) child: Operation not permitted (1). Generate and load a local policy module:

# Build a policy from denied audit entries
grep ptrace /var/log/audit/audit.log | audit2allow -M php_ptrace
semodule -i php_ptrace.pp

Empty slow log with no errors (user isolation). If the pool runs as a non-root user and the master runs as root, process.dumpable must be yes (see step 5 above). Without it, ptrace fails silently. This is common on shared hosting and cPanel-style deployments where the master and pool users differ.

request_terminate_timeout fires first. If request_terminate_timeout is set lower than request_slowlog_timeout, the master kills the worker before the slow log can capture the trace. Always keep request_slowlog_timeout below request_terminate_timeout (for example, 5s versus 30s). If you see workers dying with no slow log entry and request_terminate_timeout is configured, check the ordering.

Log rotation breaks the slow log. If the slow log is rotated without sending SIGUSR1 to the master, FPM keeps the old (now deleted) file descriptor open and writes go nowhere. Configure logrotate to signal the master:

postrotate
    kill -USR1 $(cat /run/php-fpm.pid 2>/dev/null) 2>/dev/null || true
endscript

If your slow log suddenly stops receiving entries after a logrotate run, this is why.

Worker stuck after tracing (older PHP). There are reports of workers stuck in “Processing” state after being stopped for slow-log tracing on older PHP 7.x releases. If you run PHP 7.4 or 8.0 and see workers that never return to idle after enabling the slow log, treat this as a candidate cause. Upgrade to a maintained 8.x release, or disable the slow log until you can.

Log integrity note (CVE-2024-9026). A low-severity vulnerability (CVSS 3.3) in PHP-FPM allows limited log manipulation when catch_workers_output = yes is set. It was patched in PHP 8.1.30, 8.2.24, and 8.3.12. It does not directly affect slow log integrity, but it is relevant when you are enabling or auditing FPM logging features. Run a patched version.

Signals to monitor

SignalWhy it mattersWarning sign
slow requests counter (rate)Direct indicator of requests exceeding the thresholdSustained non-zero rate, or rate above 2x rolling baseline
Slow log script and line concentrationLocalizes the slow code pathOne script or function appearing repeatedly across entries
active processes near pm.max_childrenShows slow requests are consuming worker capacityActive climbing in lockstep with the slow request rate
Listen queue depthConfirms slow requests are causing user-visible queuingNon-zero and growing while slow requests increment
Per-worker request duration (full status)Shows bimodal distribution: fast normal requests plus stuck outliersMultiple workers at 10x the median duration
Slow log entry volume per hourTracks regression or backend degradation over timeStep change after a deploy or dependency incident

Correlate the counter rate with the slow log file. The counter tells you when to look. The file tells you where to look.

How Netdata helps

  • Per-second polling of the slow requests counter catches rate spikes that a 10 or 30 second poll interval misses. PHP-FPM saturation events unfold in seconds, and the slow request rate is the leading indicator before the listen queue fills.
  • Correlation in one view: the slow request rate sits alongside active processes, idle processes, listen queue depth, and per-worker request duration, so you can see whether slow requests are driving worker exhaustion without pivoting between tools.
  • Anomaly detection on the slow request rate flags sudden shifts without hand-tuned thresholds, which matters because the right threshold depends on your application’s normal latency profile.
  • Rate-of-change alerts on max children reached and the listen queue complement the slow log: the slow log explains why workers are stuck, while the queue and max-children counters show when that stuckness is about to spill over into user-visible errors.
  • Per-pool visibility when you run multiple pools, so a slow log spike in one pool is not masked by healthy aggregates in another.