What does pm.max_requests do in a PHP-FPM pool, and why is it set to work around memory growth?
answer
- default 0 means never respawn
- worker exits after N requests
- master forks a replacement
- caps leaks, does not fix them
- per-process state is lost
basics
~20 spm.max_requests makes each PHP-FPM worker exit after serving that many requests, and the master forks a replacement. The default 0 never recycles. It limits memory that leaks or accumulates inside a long-lived worker, without fixing the cause.
solid answer
~40 sPHP frees a request's variables at the end of the request, but a worker process lives on, and some memory can grow across requests: leaks in extensions or libraries, caches kept in static properties, fragmentation. `pm.max_requests` bounds that growth: after a worker has served N requests it finishes the current one and exits, and the master forks a fresh worker. The default is `0`, meaning workers are never recycled for this reason. A value such as 500–1000 is common when workers visibly grow. The costs: a fork per recycle, and loss of per-process state such as persistent database connections and PHP's realpath cache; OPcache is shared memory, so it stays warm. It is a mitigation, so still find the leak, and remember it does nothing about a single request that exhausts `memory_limit`.
code
ini · 8 lines[app]
pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
; workers grow about 0.2 MB per request; cap growth near 100 MB
pm.max_requests = 500go deeper
Recall that pm.max_requests restarts a worker after a set number of requests and that 0, the default, never does.
Explain why memory can grow across requests despite share-nothing, what a recycle costs, and why OPcache survives it.
Choose N from measured growth, size max_children on aged workers, and keep treating the leak as a bug to find rather than a setting to tune.
Weigh recycling as permanent policy against the effort of removing leaking components, and set how growth is monitored across the fleet.
## Why worker memory can grow at all PHP's request lifecycle is **share-nothing**: at the end of each request the engine destroys the request's variables and objects and frees their memory. Yet a PHP-FPM **worker process** survives from one request to the next, and some memory lives at process level: - **Leaks in extensions or native libraries** that allocate outside PHP's per-request memory manager and never free it. - **Process-level caches** that legitimately grow — PHP's realpath cache, extension-level caches, persistent connections. - **Heap fragmentation**: after big requests, the process may not return memory to the operating system even when PHP freed it. The result: a worker that started at 30 MB is at 200 MB after a day. Multiply by `pm.max_children` and the server that fitted comfortably starts swapping. ## What pm.max_requests does `pm.max_requests` is a per-pool integer: 1. Each worker counts the requests it has served. 2. When the count reaches the value, the worker finishes the current request, closes the connection cleanly and **exits**. 3. The master notices the exit and **forks a replacement**, which starts from a fresh process image. The default is **`0`**, which means "endless request processing" — workers are never recycled for this reason. The PHP-FPM sample configuration describes the setting as useful "to work around memory leaks in 3rd party libraries", and it is the FPM counterpart of the old `PHP_FCGI_MAX_REQUESTS` environment variable. | Value | Effect | |---|---| | `0` (default) | Workers live until FPM reloads or they crash | | Low, e.g. 50 | Frequent forks; a fork cost on a noticeable share of requests | | Moderate, e.g. 500–1000 | Growth is capped with a negligible recycle rate | ## What recycling costs - **A fork.** The master forks a new worker from its already-initialised image, so this is cheap compared with starting PHP from scratch, but not free. - **Lost per-process state.** Persistent database connections held by the worker close and must be reopened; the realpath cache and any in-process cache start empty. - **OPcache is not lost.** Its compiled scripts live in shared memory owned by the master, so a new worker uses them immediately. - **Clustering.** Workers that start together and receive similar numbers of requests may reach the limit at similar times, so several recycles can happen close together; a moderate value keeps that a minor effect. ## Seeing it in the log With FPM's default `log_level = notice`, each recycle leaves two lines in the FPM error log: `[pool app] child 1234 exited with code 0 after 812.345678 seconds from start` and `[pool app] child 1240 started`. Exit code 0 is the normal end of a recycled worker; a non-zero code or a signal is logged as a WARNING and points to a crash instead. Counting these lines per hour is a simple check that the chosen N matches the traffic. ## What it does not do - It does **not** stop a single request from using too much memory — that is `memory_limit` (default `128M`), which aborts the request with a fatal error. - It does **not** fix the leak. It makes the leak's peak predictable: memory per worker is bounded by roughly "fresh size + leak per request × N". - It does **not** help memory that a request holds while running, such as loading a whole CSV into an array; that is the request's own footprint. ## Using it well 1. **Measure first.** Record worker memory against the number of requests served. Flat memory means you do not need the setting at all. 2. **Pick N from the slope.** If a worker grows 0.2 MB per request and you can afford 100 MB of growth, N ≈ 500. 3. **Keep hunting the cause.** Disable suspect extensions on a staging pool, compare growth, and upgrade or replace the leaking component. 4. **Re-size after changes.** `pm.max_children` sizing should use the memory of an *aged* worker, near the end of its N requests, not a fresh one. ## In long-running PHP servers Worker-mode runtimes that keep the application booted across requests face the same growth problem more sharply, and have their own "restart the worker after N requests" settings; those runtimes are a separate subject. In PHP-FPM, `pm.max_requests` is the whole mechanism.
- After setting pm.max_requests = 500, a report page still dies with 'Allowed memory size exhausted'. Why?`pm.max_requests` only limits growth that accumulates across requests in one worker. The fatal error comes from `memory_limit`, which caps the memory a single request may allocate. That request needs too much by itself — for example it loads the whole data set into an array — so it must use less memory or get a higher limit.
- Does recycling a worker throw away OPcache and slow the next request?No. OPcache keeps compiled scripts in shared memory set up by the FPM master, so a freshly forked worker uses them at once. What a recycle does lose is per-process state, such as persistent database connections and the realpath cache.
saying these in an interview costs you the question
- pm.max_requests limits how many requests a worker handles at the same time
- The default pm.max_requests is 500
- Setting pm.max_requests fixes the memory leak
- Recycling a worker clears OPcache for that worker
- pm.max_requests stops a single request from exceeding memory_limit