PHP-FPM tuning: Using ‘pm static’ for max performance
Let’s take a very quick look at how best to set up PHP-FPM for high throughput, low latency, and more stable CPU and memory use. 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, let’s compare the two management options based on php.net’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 complete list of global php-fpm.conf directives for further details.
Table of Contents
PHP-FPM process manager (PM) similarities to CPUFreq Governor
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, 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, let’s compare the descriptions and look for similarities:
Governor = ondemand – Scales CPU frequency dynamically according to the current load. It jumps to the highest frequency and then scales down as the idle time increases.
Governor = conservative = Scales the frequency dynamically according to the current load. Scales the frequency more gradually than ondemand.
Governor = performance – Always run the CPU at the maximum frequency.
See the full list of CPUFreq governor options for further details.
Notice the similarities? I wanted to use this comparison first, intending to find the best way to write an article that recommends using pm static for PHP-FPM as your first choice.
With CPU governor, the performance setting is a safe performance boost because it’s almost entirely dependent on your server CPU’s limit. The only other factors would be heat, battery life (laptop), and other side effects of permanently clocking your CPU frequency to 100%.
Once set to performance, it is 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. If you suffer 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.
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, which are 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 the current traffic.
The idle processes stay online waiting for traffic spikes and respond 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 number of processes displayed are only the ‘top’ 50 or so (didn’t count), but 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 challenging 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.
However, PM dynamic and especially ondemand can save you when you have multiple PHP-FPM pools—for example, hosting multiple cPanel accounts or 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 dynamic to perform well. Only ondemand performs well since more than two-thirds of the websites receive little to no traffic and with ondemand, it means all children will be shut down, saving tons of server memory!
Thankfully, cPanel’s 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).
Conclusion
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 fewer 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, the more automated overhead process managers – dynamic and ondemand – are more popular recommendations. I hope this was a helpful 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.
Originally published: Oct 10th, 2017 | Last updated: June 26th, 2023
Hi @hydn
Thanks for the article, really helpful.
Have you considered the impact of these settings in a modern containerised environment?
I’m running a single PHP container for a low traffic website. Over time this traffic will grow, and at some point, will be fairly substantial.
I’m using the default settings - dynamic, 5 children, etc. - on a container with 0.5 vCPU and 1GB RAM.
I would like to be able to horizontally scale these containers so that when traffic increases I can bring new containers online. Ideally, I would do this automatically, based on the amount of CPU or memory the current container(s) are using (e.g. once they hit a threshold of 80%, a new container comes online).
What settings would you recommend for this environment?
@dunc
When scaling using the hardware (0.5 vCPU and 1GB RAM) then no this article wouldn’t really apply. You can provide feedback and recommendations you find to work well for other readers.
Thanks for the feedback.
Hi @dunc
The pm.static setting is important for this environment and probably the only one possible.
For scaling, you then need to use not only CPU and RAM values, but also the number of active PHP processes. Because if pm.max_children is set to 50 and there are 40 active processes and 10 idle, it’s time to start another container regardless of RAM and CPU consumption.
So, for scaling purposes, we need to keep php status active and evaluate the values of active (or idle) PHP processes periodically at short intervals.
The specific setting depends on the technology used, if it’s some containerization system or kubernetes.