PHP-FPM tuning: Using ‘pm static’ for max performance

Lets take a very quick look at how best to setup PHP-FPM for high throughput, low latency and a more stable use of CPU and memory. By default, most setups have PHP-FPM’s PM (process manager) string set to dynamic and there’s also the common advice to use ondemand if you suffer from available memory issues. However, lets compare the two management options based on’s documentation and also compare my favorite for high traffic setups… static pm:

pm = dynamic – the number of child processes is set dynamically based on the following directives: pm.max_children, pm.start_servers,pm.min_spare_servers, pm.max_spare_servers.

pm = ondemand – the processes spawn on demand (when requested, as opposed to dynamic, where pm.start_servers are started when the service is started.

pm = static – the number of child processes is fixed by pm.max_children.

See the full list of global php-fpm.conf directives for further details.


PHP-FPM process manager (PM) similarities to CPUFreq Governor

Now, this may seem a bit off topic, but I hope to tie it back into our PHP-FPM tuning topic. Ok, we’ve all had slow CPU issues at some point, whether it be a laptop, VM or dedicated server. Remember CPU frequency scaling? (CPUFreq governor) These settings, available on both *nix and Windows can improve the performance and system responsiveness by changing the CPU governor setting from ondemand to performance. This time, lets compare the descriptions and look for similarities:

Governor = ondemand – Scales CPU frequency dynamically according to current load. Jumps to the highest frequency and then scales down as the idle time increases.

Governor = conservative =  Scales the frequency dynamically according to current load. Scales the frequency more gradually than ondemand.

Governor = performance – Always run the CPU at the maximum frequency.

See full list of CPUFreq governor options for further details.

Notice the similarities? I wanted to use this comparison first, with the aim of finding the best way to write an article which recommends using pm static for PHP-FPM as your first choice.

With CPU governor, the performance setting is a pretty safe performance boost because it’s almost entirely dependent on your server CPU’s limit. The only other factors would be things such as heat, battery life (laptop) and other side effects of clocking your CPU frequency to 100% permanently. Once set to performance, it is indeed the fastest setting for your CPU. For example read about the ‘force_turbo’ setting on Raspberry Pi which forces your RPi board to use the performance governor where performance improvement is more noticeable due to the low CPU clock speeds.


Using ‘pm static’ to achieve your server’s max performance

The PHP-FPM pm static setting depends heavily on how much free memory your server has. Basically if you are suffering from low server memory, then pm ondemand or dynamic maybe be better options. On the other hand, if you have the memory available you can avoid much of the PHP process manager (PM) overhead by setting pm static to the max capacity of your server. In other words, when you do the math, pm.static should be set to the max amount of PHP-FPM processes that can run without creating memory availability or cache pressure issues. Also, not so high as to overwhelm CPU(s) and have a pile of pending PHP-FPM operations. 

Linux top php-fpm static pm

In the screenshot above this server has pm = static and pm.max_children = 100 which uses a max of around 10GB of the 32GB installed. Take note of the highlighted columns, self explanatory. During that screenshot there were about 200 ‘active users’ (past 60 seconds) in Google Analytics. At that level, about 70% of PHP-FPM children are still idle. This means PHP-FPM is always set to the max capacity of your server’s resources regardless of current traffic. Idle process stay online waiting for traffic spikes and responding immediately, rather than having to wait on the pm to spawn children and then kill them off after x pm.process_idle_timeout expires. I have pm.max_requests set extremely high because this is a production server with no PHP memory leaks. You can use pm.max_requests = 0 with static if you have 110% confidence in your current and future PHP scripts. However, it’s recommended to restart scripts over time. Set the # of requests to a high number since the point is to avoid pm overhead. So for example at least  pm.max_requests = 1000 …depending on your # of pm.max_children and # of requests per second.

The screenshot uses Linux top filtered by ‘u’ (user) option and the name of the PHP-FPM user. The # of processes displayed are only the ‘top’ 50 or so (didn’t count) but basically top displays the top stats which fit in your terminal window. In this case sorted by %CPU. To view all 100 PHP-FPM processes you can use something like:

top -bn1 | grep php-fpm


When to use pm ‘ondemand’ and ‘dynamic’

Using pm dynamic you may have noticed errors similar to:

WARNING: [pool xxxx] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 32 children, there are 4 idle, and 59 total children

You may try to increase/adjust settings and still see the same error as someone describes in this Serverfault post . In that case, the pm.min was too low and because web traffic fluctuates greatly with dips and spikes, using pm dynamic can be difficult to tune correctly. The common advice is to use pm ondemand, as is the advice in this same support thread. However, that’s even worse, because ondemand will shutdown idle processes right down to 0 when there’s little to no traffic and then you’ll end up with just as much overhead issues as traffic fluctuates. Unless, of course you set the idle timeout extremely high. In which case you should just be using pm.static + a high pm.max_requests.

PM dynamic and especially ondemand can be save you however, when you have multiple PHP-FPM pools. For example, hosting multiple cPanel accounts or multiple websites under different pools. I have a server for example with 100+ cpanel accounts and about 200+ domains and it would be impossible for pm.static or even dynamic to perform well. Only ondemand performs well since more than two third’s of the websites receive little to no traffic and with ondemand it means all children will be shutdown saving tons of server memory! Thankfully, cPanel devs figured this out and now defaults to ondemand. Previously with dynamic as default it made PHP-FPM not an option on busy shared servers. Many would use suPHP because of pm dynamic eating up memory even on idle cPanel PHP-FPM pools/accounts. Chances are, if you receive good traffic, you won’t be hosted on a server with lots of PHP-FPM pools (shared hosting).



When it comes to PHP-FPM, once you start to serve serious traffic, ondemand and dynamic process managers for PHP-FPM can limit throughput because of the inherent overhead. Know your system and set your PHP-FPM processes to match your server’s max capacity. Start with pm.max_children set based on max usage of pm dynamic or ondemand and then increase to the point where memory and CPU can process without becoming overwhelmed. You will notice that with pm static, because you keep everything sitting in memory, traffic spikes over time cause less spikes to CPU and your server’s load and CPU averages will be smoother. The average size of your PHP-FPM process will vary per web server requiring manual tuning, thus why the more automated overhead process managers – dynamic and ondemand – are more popular recommendations.  Hope this was a useful article.

Update: Added ab benchmark comparison graph. Having PHP-FPM processes sit in memory helps performance at the price of increased memory usage to have them sit in wait. Find your setup sweet-spot.

PHP-FPM PM comparison

Published: Oct 10th 2017 | Updated: April 23rd 2019

Tags: , , ,

PHP-FPM tuning: Using ‘pm static’ for max performance

20 Responses

  1. I wanted to like this article, but you provide no benchmarks to justify your theory or even to show what percentage of benefits one might see..

    elchorizo October 16, 2017 at 7:33 am #
    • Thanks for stopping by! I’m a bit wary of benchmarks sometimes as they can encourage a false sense that the results will apply to every case. I went back and generated benchmark graph using ab. However, I would still suggest with or without benchmarks we always confirm performance per setup/environment. Factors such as average memory size of PHP-FPM processes, server memory available, PHP code/scripts, PHP version, traffic patterns, # of slow scripts, memory leaks, etc, etc can impact results.

      Hayden James October 18, 2017 at 1:13 pm #
  2. I do not see the any reason to use dynamic or ondemand if it’s critical to make sure the app will run without problems. I still think that dynamic and ondemand are there only for the sake of people doing shared hosting.

    Madalin Ignisca October 19, 2017 at 11:23 am #
    • I’ve been using this method for years and it’s not for just shared hosting. It works without issues or problems and if you set it up correctly you can get super high output and performance.

      Brightcluster June 25, 2018 at 5:22 pm #
  3. Assuming a dedicated application server (i.e. no DB engine running on it so memory will be shared across webserver, php-fpm and linux), and a busy site, I wonder if, taking the key concept of your article, pm dynamic with a considerable value to have enough ready-to-go processes (maybe pm.start_servers = pm.max_children) wouldn’t be a good trade-off between performance and flexibility. Massive static allocation of RAM could make Linux to swap for memory that might be reserved for unused php-fpm processes. Does it make any sense?

    Fernando Socca November 20, 2017 at 11:59 am #
    • Hi Fernando, Good question. However, you said:
      “…taking the key concept of your article, pm dynamic with a considerable value to have enough ready-to-go processes (maybe pm.start_servers = pm.max_children) wouldn’t be a good trade-off between performance and flexibility”

      Then what would be the point/benefit of using pm.dynamic over pm.static? Regardless of PM used, the pm.max_children should never be set to the point where it increases the server’s cache pressure too high, especially to the point of swapping. Thus using dynamic will only achieve a fluctuation in memory usage. Something that’s only necessary if your server does not have adequate RAM in which case your possible pm.max_children will be hampered anyway. Using pm static will allow you to make use of what you have in a more stable manner without killing children (purging PHP-FPM processes and PHP-FPM cached memory) which on busy servers will help reduce any delays in spawning new children. New spawns cause more CPU spikes vs a FPM process which are already sitting in memory idle in wait.

      Hayden James November 20, 2017 at 1:34 pm #
    • Hi. Thank you for your reply. My point simply is, maybe pm dynamic with a high number of started processes (same amount of planned static approach) might behave exactly as static would do, but with the extra benefit that above those processes it floates to accomodate potential need of more workers (only in extreme cases), otherwise keeping that RAM free for another modules that might also need it in a spike of memory demand. Imagine taking 70% of RAM for started php-fpm workers (assuming Apache Event MPM), but leaving 20% for potential grow. That 20% could be used as an overflow for php-fpm in case it needs those workers, or be kept free so other module can see it as available RAM. So you can have all the benefits stated in your article (I agree with it!) plus some room to grow if needed. Best of both worlds?

      Fernando Socca November 20, 2017 at 2:02 pm #
    • Yes. Can work. If you have the free RAM or enough space capacity for traffic increases. This is fine and somewhat protects from spikes like Reddit traffic or some other traffic event. I’d say that’s a fair compromise. Just check your logs for any clues otherwise.

      Hayden James November 20, 2017 at 2:43 pm #
    • cool, thanks for your comments. I’m designing and implementing right now an app server and I’m considering this approach but definitely I’ll stick to the idea of fixed workers ready to answer and not spawning them on demand.

      Fernando Socca November 20, 2017 at 2:56 pm #
    • Thanks for sharing and discussing config setup. Will be useful for readers.

      Hayden James November 20, 2017 at 3:10 pm #
  4. Great article, I recently upgraded my server to use suPHP with FPM and started experiencing problems with Gateway Timeouts on a particular site that has quite a heavy load on traffic. After reading your article I updated my PHP-FPM configuration and so far I have not experienced any timeouts. Thanks for the article.

    SNETTSCOM May 1, 2018 at 9:38 am #
  5. any formula to work out max_requests when using static?

    Vip3r011 June 19, 2018 at 1:05 pm #
    • That depends only on your php application.

      Madalin Ignisca July 30, 2018 at 9:19 pm #
    • You will need a data that shows the usage of your application. both CPU and Memory. (monitoring like Nagios or Check_mk). Add a webserver status stub (if apache or nginx) Correlate the data when traffic was high. You can then figure out a value for max processes.

      If you don’t have an historic data, then its best to start populating now. Secondly, you can find avg memory consumed by php-fpm process, which should give you an idea of your application’s consumption. Add 10% as safe buffer to avg memory consumption and set the number of process. You can use ps and awk to summarize memory usage (rss) (

      Mayur Diman August 13, 2018 at 6:49 am #
  6. Gracias por tu articulo, voy a probar el sistema static. Gracias

    Javier Fernandez Gonzalez August 7, 2018 at 11:34 am #
  7. We have a website with 500 concurrent connections, and sometimes spiked to 1000 concurrent connections. Previously we use VestaCP with default configurations (Apache + NGINX) and no problem with high traffic.

    Now we use pure NGINX + PHP-FPM, but sometimes having error like “(11: Resource temporarily unavailable) while connecting to upstream”. It turns out using default PHP-FPM config is not suitable with our website condition. With server specs 4 core and 16GB RAM, we try this configuration:

    pm = ondemand
    pm.max_children = 256 (based on calculation each php-fpm process using ~40mb)
    pm.process_idle_timeout = 15
    pm.max_requests = 6000
    pm.start_servers = 8
    pm.min_spare_servers = 5
    pm.max_spare_servers = 10

    However, when we stress test the website using with 1000 users, CPU usage spiked to almost 100%, but RAM usage is ONLY around 3GB (that also include MySQL)!
    Why PHP-FPM process use so little RAM, as if there is something limit the process? Can we max out RAM usage to reduce CPU usage? We don’t want using pm static because the server host several accounts/websites.
    Any help would be greatly appreciated.

    NanoG6 August 26, 2018 at 2:00 am #
    • How are you calculating “concurrent” connections? :)
      Please send me an email if you still require a solution to your issues >

      Hayden James October 10, 2018 at 1:00 pm #
    • I’m getting a similar issue to NanoG6, do you remember the solution you guys found? I get different results with different load testing services, gives me 100% cpu usage and begins running into 499 errors very quickly. However Octoperf gives more favorable results and doesn’t hit my cpu as hard.

      Aaron November 13, 2018 at 11:23 pm #
  8. Great article, James! Could you please share how you’ve created ab comparison benchmark?

    icamys October 8, 2018 at 9:09 am #
    • Thanks!! Please see previous reponse to benchmark:
      “Thanks for stopping by! I’m a bit wary of benchmarks sometimes as they can encourage a false sense that the results will apply to every case. I went back and generated benchmark graph using ab. However, I would still suggest with or without benchmarks we always confirm performance per setup/environment. Factors such as average memory size of PHP-FPM processes, server memory available, PHP code/scripts, PHP version, traffic patterns, # of slow scripts, memory leaks, etc, etc can impact results.”

      I ran the ab command from command line locally on the server. exported results to graph.

      Hayden James October 8, 2018 at 10:47 am #

Leave a Reply