# [RFC] Introducing pm.max_memory for PHP-FPM

- Thread: https://externals.io/message/126226
- Original archive: https://news-web.php.net/php.internals/126226
- Started by Arkadiy Kulev on 2025-01-28T01:13:35+00:00
- Messages: 10
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #126226 — Arkadiy Kulev — 2025-01-28T01:13:35+00:00

URL: https://externals.io/message/126226#126226

Hello everyone,

I’d like to propose adding a new configuration directive, tentatively
called *pm.max_memory*, to PHP-FPM. This directive would allow
administrators to specify a per-child memory usage limit, after which the
PHP-FPM child process would gracefully exit or restart.
Background and Rationale

In many production environments, especially those handling long-running or
memory-intensive processes, *memory leaks* (whether in extensions,
libraries, or user code) can slowly accumulate. We already have tools like
pm.max_requests that recycle processes after a certain number of requests,
but there are scenarios in which memory usage might skyrocket within fewer
requests—or memory might slowly climb in a way that doesn’t align well with
request counts alone.

A setting like pm.max_memory could:

   1. *Mitigate slow leaks:* By enforcing a hard memory cap at the process
   level, ensuring no single worker balloons in size over time.
   2. *Provide more granular control:* If certain scripts or pools are
   known to be memory-intensive, an admin could set a memory limit that’s
   appropriate for that usage pattern, rather than tying it only to request
   counts.
   3. *Complement system-level controls:* While cgroups and container
   memory limits exist, a built-in FPM mechanism can be friendlier than a
   system OOM kill, which might be abrupt and less predictable.

Proposed Behavior

   - A new config directive, *pm.max_memory*, which, if set to a value
   above 0, indicates the maximum amount of RAM (in bytes) a PHP-FPM worker
   process is allowed to use (resident set size or a similar metric).
   - After handling a request, each worker would check its own memory
   usage. If it exceeds the threshold, it would gracefully exit or be
   terminated by the master process before picking up a new request.
   - By default, this setting could be *disabled* (pm.max_memory = 0), so
   it does not affect existing installations.

Implementation Details and Challenges

I am not proposing to implement this feature myself—I’m more of a sysadmin
and don’t have the necessary C knowledge to patch php-src. However, here
are some thoughts on what the implementation might involve:

   1. *Measuring memory usage:* Likely via `getrusage()`, mallinfo() (on some
   platforms), or reading from /proc/self/statm on Linux.
   2. *Graceful shutdown logic:* Ensuring no ongoing requests are abruptly
   killed, or at least minimizing the chance of partial request handling.
   3. *Platform differences:* Some OSes might provide different APIs for
   measuring process memory usage. We’d need consistent cross-platform
   behavior or documented differences.
   4. *Interaction with pm.max_requests:* If both are set, a worker would
   exit on whichever limit it hits first (memory or request count).

Alternatives

   - *Using pm.max_requests:* Currently the main workaround to mitigate
   leaks, but it’s less precise and can’t handle large memory spikes that
   happen quickly.
   - *System-level OOM or cgroups:* This approach can kill the entire pool
   or container, which is often more disruptive than recycling a single worker.

Request for Feedback

I’m posting this proposal to gather feedback on feasibility and interest.
If there’s enough support, I’d be happy to collaborate with anyone who can
handle the technical side—writing up a formal RFC on the wiki or working on
a patch. If there’s a consensus that this is better handled elsewhere
(system-level or container-level controls), or that pm.max_requests is
sufficient, please let me know your thoughts.

*Key questions*:

   1. Would a built-in memory cap be beneficial for a significant subset of
   PHP-FPM users?
   2. Are there any major technical hurdles or objections to this approach?
   3. Does anyone have suggestions on how best to measure memory usage
   accurately and portably across different platforms?

Thank you for reading and considering this idea. I look forward to hearing
your insights and am happy to clarify or discuss any aspect of this
proposal further.

Best regards,
Sincerely,
Arkadiy Kulev (Ark)

---

## #126239 — Jakub Zelenka — 2025-01-30T12:42:47+00:00

URL: https://externals.io/message/126226#126239 · In reply to: #126226

Hi,



> Hello everyone,
>
> I’d like to propose adding a new configuration directive, tentatively
> called *pm.max_memory*, to PHP-FPM. This directive would allow
> administrators to specify a per-child memory usage limit, after which the
> PHP-FPM child process would gracefully exit or restart.
>

I think it's reasonable as an additional protection and a bit nicer
than pm.max_requests. However, implementation might be tricky.


> Implementation Details and Challenges
>
> I am not proposing to implement this feature myself—I’m more of a sysadmin
> and don’t have the necessary C knowledge to patch php-src. However, here
> are some thoughts on what the implementation might involve:
>
>    1. *Measuring memory usage:* Likely via `getrusage()`, mallinfo() (on
>    some platforms), or reading from /proc/self/statm on Linux.
>
>
This wouldn't really work because FPM does not control the script during
execution and would have to check it out after each allocation which is not
really viable.


>
>    - *System-level OOM or cgroups:* This approach can kill the entire
>    pool or container, which is often more disruptive than recycling a single
>    worker.
>
>
I think this is really the only way that I can see to make it work
reliably. We have got already an old feature request for that:
https://bugs.php.net/bug.php?id=70605 . There was even a PR
https://github.com/php/php-src/pull/2440 but it was using unmaintained lib
and cgroup v1. We should use something cgroup v2 based but it is not a
small job to add it.

Anyway you can create a GitHub feature request for this so it doesn't get
forgotten and maybe one day there will a better PR that can be accepted.

Cheers

Jakub

---

## #126244 — Arkadiy Kulev — 2025-01-30T16:41:53+00:00

URL: https://externals.io/message/126226#126244 · In reply to: #126239

Hello!

This wouldn't really work because FPM does not control the script during
> execution and would have to check it out after each allocation which is not
> really viable.
>

Thanks for the feedback! I agree that monitoring memory usage after each
allocation would be infeasible. However, my suggestion was actually to
check memory usage *only once per request*, specifically at request
shutdown, when FPM regains control and before assigning another request to
that worker.

We already have hooks for request startup and request shutdown in FPM.
Could we simply insert a memory check at the request shutdown stage—where
the worker is returning control to the FPM master process—before picking up
a new request?

Just to be clear, "memory_limit" helps kill runaway scripts mid-request. By
contrast, the newly proposed pm.max_memory is meant to catch processes with
a slow leak across multiple requests. We only need to check at the end of
each request, which is presumably when the worker returns control to FPM.

---

## #126247 — Rob Landers — 2025-01-30T18:11:00+00:00

URL: https://externals.io/message/126226#126247 · In reply to: #126244

> Hello!
> > […]
> 
> Thanks for the feedback! I agree that monitoring memory usage after each allocation would be infeasible. However, my suggestion was actually to check memory usage *only once per request*, specifically at request shutdown, when FPM regains control and before assigning another request to that worker. 
> 
> We already have hooks for request startup and request shutdown in FPM. Could we simply insert a memory check at the request shutdown stage—where the worker is returning control to the FPM master process—before picking up a new request?
> 
> Just to be clear, "memory_limit" helps kill runaway scripts mid-request. By contrast, the newly proposed pm.max_memory is meant to catch processes with a slow leak across multiple requests. We only need to check at the end of each request, which is presumably when the worker returns control to FPM.

To be honest, I haven't seen a 'slow' memory leak in a long time -- except when using fgetcsv which has had a reported memory leak since ~2012 somewhere on the old bug tracker. (I lost the link to it and don't even remember how to get to the tracker or if it still exists.) I haven't checked if it has been fixed since 8.2, but I've seen a couple of reports of it on r/php a couple of times in the last couple of years.

— Rob

---

## #126248 — Arkadiy Kulev — 2025-01-30T18:16:35+00:00

URL: https://externals.io/message/126226#126248 · In reply to: #126247

>
> To be honest, I haven't seen a 'slow' memory leak in a long time -- except
> when using fgetcsv which has had a reported memory leak since ~2012
> somewhere on the old bug tracker. (I lost the link to it and don't even
> remember how to get to the tracker or if it still exists.) I haven't
> checked if it has been fixed since 8.2, but I've seen a couple of reports
> of it on r/php a couple of times in the last couple of years.
>

A significant number of production environments still run PHP 7.4. Also,
beyond the standard extensions, many installations rely on third-party or
custom C extensions for specialized tasks (e.g., image processing, machine
learning, or connections to external systems). Even a small bug in those C
bindings can cause slow leaks that accumulate over multiple requests. In
these cases, a process-level memory limit (checked after each request) can
help avoid excessive memory growth without waiting on a fix or implementing
a complicated workaround. Though it might be rare in core PHP these days,
the fact that such issues can arise (especially in non-core extensions) is
one of the main reasons some users might like a pm.max_memory mechanism.

---

## #126254 — Jakub Zelenka — 2025-01-31T10:14:39+00:00

URL: https://externals.io/message/126226#126254 · In reply to: #126244

Hi,


> This wouldn't really work because FPM does not control the script during
> > […]
>
> Thanks for the feedback! I agree that monitoring memory usage after each
> allocation would be infeasible. However, my suggestion was actually to
> check memory usage *only once per request*, specifically at request
> shutdown, when FPM regains control and before assigning another request to
> that worker.
>
>
I think that would require a different name because it would not reflect
max memory usage in any way - it would be measured at the time when the
memory usage is lowest. We could maybe set some options that would measure
the maximum of increase of memory between requests - I mean difference
between lowest memory usage (most likely after the first request) and then
compare against current usage after the latest request and set limit on
this difference. Not sure about the name for that. Maybe something like
pm.max_memory_increase or something like that.


> We already have hooks for request startup and request shutdown in FPM.
> Could we simply insert a memory check at the request shutdown stage—where
> the worker is returning control to the FPM master process—before picking up
> a new request?
>
>
Yeah that would be possible.


> Just to be clear, "memory_limit" helps kill runaway scripts mid-request.
> By contrast, the newly proposed pm.max_memory is meant to catch processes
> with a slow leak across multiple requests. We only need to check at the end
> of each request, which is presumably when the worker returns control to FPM.
>

There is one thing to note that memory_limit actually measure only memory
allocated through the per request php memory allocator so it's not actually
limit on total usage including the standard allocator memory usage. So
there would be still a use case for total limit using cgroups but I agree
that the more important use is to catch slow leaks which the above should
help with in a better way than pm.max_requests.

Regards,

Jakub

---

## #126255 — Jakub Zelenka — 2025-01-31T10:26:05+00:00

URL: https://externals.io/message/126226#126255 · In reply to: #126247

>
>
> To be honest, I haven't seen a 'slow' memory leak in a long time -- except
> when using fgetcsv which has had a reported memory leak since ~2012
> somewhere on the old bug tracker. (I lost the link to it and don't even
> remember how to get to the tracker or if it still exists.) I haven't
> checked if it has been fixed since 8.2, but I've seen a couple of reports
> of it on r/php a couple of times in the last couple of years.
>

I think it might still happen from time to time in external extensions.
Just recently Niels fixed this one
https://github.com/php/pecl-xml-xmldiff/pull/3 . In addition there can be
also a bug in an external library so it's not unlikely that there are still
many such cases.

Currently many people just have pm.max_requests in configuration for that
which is sometimes set to unnecessary low value. Having option that depends
on memory instead could reduce significantly the number of restarts - in
most cases probably to zero. In addition, it could also help to identify
that there is a leak which can get unnoticed if the users don't have alarms
on memory usage or just use pm.max_requests.

Regards

Jakub

---

## #126259 — Arkadiy Kulev — 2025-01-31T15:06:19+00:00

URL: https://externals.io/message/126226#126259 · In reply to: #126254

Hi!


> This wouldn't really work because FPM does not control the script during
> > […]
>
> > […]
> I think that would require a different name because it would not reflect
> max memory usage in any way - it would be measured at the time when the
> memory usage is lowest. We could maybe set some options that would measure
> the maximum of increase of memory between requests - I mean difference
> between lowest memory usage (most likely after the first request) and then
> compare against current usage after the latest request and set limit on
> this difference. Not sure about the name for that. Maybe something like
> pm.max_memory_increase or something like that.
>

I see where you’re coming from, but I believe measuring memory “delta” or
“increase” could be confusing for end users. In practice, admins and
developers often glance at tools like top or ps to see *current* memory
usage for each FPM worker, spot outliers, and then set a threshold
accordingly.

If we start talking about “lowest usage” vs. “current usage” and a “max
increase,” that becomes much harder to translate to real-world
monitoring—and it’s non-intuitive compared to simply reading the value
right off top and setting a limit. So, from a usability standpoint, I think
a direct measurement of resident memory (as people see in common system
tools) would be the most straightforward and least confusing.


> Just to be clear, "memory_limit" helps kill runaway scripts mid-request.
> > […]
>
> There is one thing to note that memory_limit actually measure only memory
> allocated through the per request php memory allocator so it's not actually
> limit on total usage including the standard allocator memory usage. So
> there would be still a use case for total limit using cgroups but I agree
> that the more important use is to catch slow leaks which the above should
> help with in a better way than pm.max_requests.
>

You’re absolutely right that cgroups handle total memory usage—including
memory outside the PHP allocator—more accurately. But as I’ve mentioned
before, relying on cgroups to limit memory typically means an OOM kill that
can happen at *any* moment, often right in the middle of a request. That’s
precisely what I’m trying to avoid.
The whole idea behind pm.max_memory is to allow a *graceful* check *after*
each request completes, so we can recycle the worker before starting a new
request. That way, no request gets abruptly killed. cgroups don’t really
accommodate that scenario—they’re great for overall resource control but
not for per-request, child-level recycling within FPM.

---

## #126260 — Jakub Zelenka — 2025-01-31T15:30:07+00:00

URL: https://externals.io/message/126226#126260 · In reply to: #126259

> Hi!
>
>
> > […]
>
> I see where you’re coming from, but I believe measuring memory “delta” or
> “increase” could be confusing for end users. In practice, admins and
> developers often glance at tools like top or ps to see *current* memory
> usage for each FPM worker, spot outliers, and then set a threshold
> accordingly.
>
> If we start talking about “lowest usage” vs. “current usage” and a “max
> increase,” that becomes much harder to translate to real-world
> monitoring—and it’s non-intuitive compared to simply reading the value
> right off top and setting a limit. So, from a usability standpoint, I
> think a direct measurement of resident memory (as people see in common
> system tools) would be the most straightforward and least confusing.
>
It's probably less confusing than setting pm.max_memory to some value and
then see that the process allocates much more. We could potentially call it
pm.max_idle_memory or something that clearly shows that it's not a total
max memory.


> Just to be clear, "memory_limit" helps kill runaway scripts mid-request.
> > […]
>
> You’re absolutely right that cgroups handle total memory usage—including
> memory outside the PHP allocator—more accurately. But as I’ve mentioned
> before, relying on cgroups to limit memory typically means an OOM kill that
> can happen at *any* moment, often right in the middle of a request.
> That’s precisely what I’m trying to avoid.
> The whole idea behind pm.max_memory is to allow a *graceful* check *after*
> each request completes, so we can recycle the worker before starting a new
> request. That way, no request gets abruptly killed. cgroups don’t really
> accommodate that scenario—they’re great for overall resource control but
> not for per-request, child-level recycling within FPM.
>

Yeah it should really be the last resort. Agreed that for this particular
case, the solution above would be better.

Regards,

Jakub

---

## #126261 — Arkadiy Kulev — 2025-01-31T21:48:42+00:00

URL: https://externals.io/message/126226#126261 · In reply to: #126260

Posted to Github: https://github.com/php/php-src/issues/17661
