PHP-FPM Tuning for Laravel Under Load

A large share of Laravel performance incidents are a pool size copied from a tutorial in 2019.

The symptom is consistent: response times are fine in the morning, degrade through the day, and recover overnight. Nothing in the application changed. What changed is concurrency, and the process pool was never sized for it.

Do the memory arithmetic first

Pool sizing is arithmetic, not taste. Measure the real memory footprint of a request under production conditions, leave genuine headroom for the database, cache and OS, and divide.

# measure actual per-process memory under load
ps --no-headers -o rss -C php-fpm | awk '{s+=$1} END {print s/NR/1024 " MB avg"}'

# max_children = (available RAM - headroom) / avg process size
# 8 GB box, 2 GB headroom, 60 MB avg  ->  ~100 children

Setting max_children too high is worse than too low. Too low queues requests; too high invites the OOM killer, and a killed FPM master is a full outage rather than a slow page.

Choosing a process manager

  • static — all children spawned at start. Predictable, no fork cost under spikes, best for steady heavy traffic
  • dynamic — the default, and a reasonable compromise, but the fork cost lands exactly during your traffic spike
  • ondemand — lowest idle memory, highest latency on a cold request. Good for low-traffic sites and staging

For a busy production application on a dedicated machine, static with correct arithmetic is usually the right answer and the easiest to reason about.

The settings people forget

  • max_requests — recycle children periodically to bound leaks in long-running extensions
  • request_terminate_timeout — must relate sensibly to the Nginx timeout, or you get 504s with orphaned work
  • slowlog — logs a stack trace for slow requests; this finds the actual culprit faster than guessing
  • OPcache validate_timestamps and memory — a misconfigured OPcache costs more than pool tuning gains
Enable the slowlog before touching anything else. It usually names the problem in an afternoon.

Nginx is part of the same system

Buffer sizes, keepalive settings and upstream timeouts interact directly with FPM behaviour. A common misconfiguration is an Nginx read timeout shorter than the FPM terminate timeout, which returns an error to the user while the work continues on the server.

Where the real wins usually are

Honest conclusion after many of these engagements: FPM tuning is frequently not the biggest win. The bigger wins are typically a missing index on a query that only became slow at production data volume, a queue worker that has been dead for two days, and no monitoring to reveal either.

That is why the sequence is measure, then monitor, then tune. In one recent case the afternoon degradation resolved entirely with configuration and query work — no rewrite, no new hardware.

From the same work

Fast in the morning, slow by afternoon?