A better way to run PHP-FPM

Mattias Geniar, Wednesday, April 9, 2014 - last modified: Monday, September 21, 2015

If you search the web for PHP-FPM configurations, you'll find many of the same configurations popping up. They nearly all use the 'dynamic' process manager and all assume you will have one master process for running PHP-FPM configurations. While there's nothing technically wrong with that, there is a better way to run PHP-FPM.

In this blogpost I'll detail;

  1. Why 'dynamic' should not be your default process manager
  2. Why it's better to have multiple PHP-FPM masters

Why you should prefer the 'ondemand' Process Manager instead of 'dynamic'

Most of the copy/paste work on the internet for PHP-FPM have configurations such as the one below.

[pool_name]
...
pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200

Most "guides" advocate the use of the 'dynamic' Process Manager ('pm' option in the config), which allows you to choose how many minimum and maximum (spare) processes you have per pool. Many guides however make blind assumptions on what your server specs are and will cause, like in the example above, a minimum of 3 PHP processes running per pool (pm.start_servers = 3). If you're on a low-traffic site, that could very well be overkill. For your server, it looks like this in your processlist.

root   3986  4704 ?   Ss   19:04   php-fpm: master process (/etc/php-fpm.conf)
user   3987  4504 ?   S    19:04   \_ php-fpm: pool pool_name
user   3987  4504 ?   S    19:04   \_ php-fpm: pool pool_name
user   3987  4504 ?        19:04   \_ php-fpm: pool pool_name

Those 3 processes will always be running, whether they're needed or not.

Ondemand Process Manager

A far better way to run PHP-FPM pools, but badly documented, would be the 'ondemand' Process Manager. As the name suggests, it does not leave processes lingering, but spawns them as they are needed. The configuration is similar to the above, but simplified.

[pool_name]
...
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 200

The 'ondemand' process manager was added since PHP 5.3.8. The config above causes your default processlist to look like this.

root   3986  4704 ?   Ss   19:04  php-fpm: master process (/etc/php-fpm.conf)

Only the master process is spawned, there are no pre-forked PHP-FPM processes. Only when processes are needed will they be started, to a maximum of 5 (with the config above, which is defined by pm.max_children). So if there are 2 simultaneous PHP requests going on, the processlist would be:

root   3986  4704 ?   Ss   19:04   php-fpm: master process (/etc/php-fpm.conf)
user   3987  4504 ?   S    19:04   \_ php-fpm: pool pool_name
user   3987  4504 ?   S    19:04   \_ php-fpm: pool pool_name

After the configured timeout in "pm.process_idle_timeout", the process will be stopped again. This does not impact PHP's max_execution_time, because the process manager only considers a process "idle" when it's not serving any request.

If you're working on a high performance PHP setup, the 'ondemand' PM may not be for you. In that case, it's wise to pre-fork your PHP-FPM processes up to the maximum your server can handle. That way, all your processes are ready to serve your requests without needing to be spawned first. However, for 90% of the sites out there, the ondemand PHP-FPM configuration is better than either static or dynamic.

A shared APC or OPcache: why multiple PHP-FPM masters are better

You may not be aware that the APC or OPcache is actually held by the master process in PHP. Any configuration for APC needs to come from the .INI configurations and cannot be overwritten later on via ini_set() or php_admin_value. That's because the spawned PHP-FPM processes have no influence on the size or config of the APC cache, as it is initiated and managed by the master process.

That inherently means that the APC/OPcache cache is shared between all PHP-FPM pools. If you only have a single site to serve, that's no issue. If you have a few dozen sites on the same server via PHP-FPM, you should be aware that they all share the same APC/OPcache cache. The APC or OPcache size should then be big enough to hold the opcode cache of all your sites combined.

To avoid this, each PHP-FPM pool can also be started separately and have it's own master process. That means each site can have its own APC or OPcache and can be started/stopped independently from all the other PHP-FPM pools. A change in one pool's config does not cause all the other FPM pools to be reloaded when the new config needs to be activated, which is the default behaviour of "/etc/init.d/php-fpm reload" (it would reload all pools).

What's needed to achieve this then;

  1. Each PHP-FPM pool needs its own init.d start/stop script
  2. Each PHP-FPM pool needs its own php-fpm.conf file to have a unique PID

If you manage your environment via a CMS such as Puppet/Chef/Salt/Ansible, the above is not difficult to set up. If you do things manually, it can become a burden and difficult to manage.

Looking at the PHP-FPM configuration

Here's what an abbreviated configuration can look like. You would now have a single .conf file that contains the configuration of your master process (PID etc.) as well as the definition of 1 PHP-FPM pool.

$ cat /etc/php-fpm.d/pool1.conf
[global]
pid = /var/run/php-fpm/pool1.pid
log_level = notice
emergency_restart_threshold = 0
emergency_restart_interval = 0
process_control_timeout = 0
daemonize = yes

[pool1]
listen = /var/run/php-fpm/pool1.sock
listen.owner = pool1
listen.group = pool1
listen.mode = 0666

user = pool1
group = pool1

pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 500

The above contains the most important bits; the main config determines that it can be daemonized and where the PID-file should be located. The Pool-configuration has the basic information of where to listen to and the type of Process Manager.

The init.d file is a simple copy/paste from the default /etc/init.d/php-fpm with a few modifications.

$ cat /etc/init.d/php-fpm-pool1
#! /bin/sh
#
# chkconfig: - 84 16
# description:  PHP FastCGI Process Manager for pool 'pool1'
# processname: php-fpm-pool1
# config: /etc/php-fpm.d/pool1.conf
# pidfile: /var/run/php-fpm/pool1.pid

# Standard LSB functions
#. /lib/lsb/init-functions

# Source function library.
. /etc/init.d/functions

# Check that networking is up.
. /etc/sysconfig/network

if [ "$NETWORKING" = "no" ]
then
    exit 0
fi

RETVAL=0
prog="php-fpm-pool1"
pidfile=/var/run/php-fpm/pool1.pid
lockfile=/var/lock/subsys/php-fpm-pool1
fpmconfig=/etc/php-fpm.d/pool1

start () {
    echo -n $"Starting $prog: "
    daemon --pidfile ${pidfile} php-fpm --fpm-config=${fpmconfig} --daemonize
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && touch ${lockfile}
}
stop () {
    echo -n $"Stopping $prog: "
    killproc -p ${pidfile} php-fpm
    RETVAL=$?
    echo
    if [ $RETVAL -eq 0 ] ; then
        rm -f ${lockfile} ${pidfile}
    fi
}

restart () {
        stop
        start
}

reload () {
    echo -n $"Reloading $prog: "
    killproc -p ${pidfile} php-fpm -USR2
    RETVAL=$?
    echo
}


# See how we were called.
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status -p ${pidfile} php-fpm
    RETVAL=$?
    ;;
  restart)
    restart
    ;;
  reload|force-reload)
    reload
    ;;
  condrestart|try-restart)
    [ -f ${lockfile} ] && restart || :
    ;;
  *)
    echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|try-restart}"
    RETVAL=2
        ;;
esac

exit $RETVAL

The only pieces we changed to that init.d script are at the top; a new process name has been defined (this needs to be unique) and the PID-file has been changed to point to our custom PID-file for this pool, as it's defined in the pool1.conf file above.

You can now start/stop this pool separately from all the others. It's configuration can be changed without impacting others. If you have multiple pools configured, your process list would look like this.

root      5963  4704 ?        Ss   19:23   0:00 php-fpm: master process (/etc/php-fpm.d/pool1.conf)
root      6036  4744 ?        Ss   19:23   0:00 php-fpm: master process (/etc/php-fpm.d/pool2.conf)

Multiple master processes are running as root and are listening to a socket defined in the pool configuration. As soon as PHP requests are made, they spawn children to handle them and stop them again after 10s of idling. The master process also shows which configuration file it loaded, making it easy to pinpoint the configuration of that particular pool.

Better separation

As soon as PHP requests are made, the processlist looks like this.

root  5963  4704  Ss  19:23  php-fpm: master process (/etc/php-fpm.d/p1.conf)
user  3987  4504  S   19:23   \_ php-fpm: pool pool1
user  3987  4504  S   19:23   \_ php-fpm: pool pool1
root  6036  4744  Ss  19:23  php-fpm: master process (/etc/php-fpm.d/p2.conf)
user  3987  4504  S   19:23   \_ php-fpm: pool pool2

To summarise, the above has 2 main advantages:

  1. a separate APC/OPcache bytecode cache per PHP-FPM pool
  2. the ability to start/stop/reconfigure PHP-FPM pools without impacting the other defined pools

For anyone struggling with APC/OPcache/realpath/stat cache issues on PHP deploys, this configuration could be a solution by allowing (sudo) access to restart or reload the master PHP-FPM process of your particular pool in order to clear all caches.

Things to keep in mind when doing this:

  • Logrotate should be modified if you use error logging in each FPM pool, to use the correct pool's PID to reload the master process;
  • Make sure all your FPM pools start on system boot, as they each have a new init.d script (check via chkconfig --list);

Feedback appreciated!



Hi! My name is Mattias Geniar. I'm a Support Manager at Nucleus Hosting in Belgium, a general web geek, public speaker and podcaster. Currently working on DNS Spy. Follow me on Twitter as @mattiasgeniar.

I respect your privacy and you won't get spam. Ever.
Just a weekly newsletter about Linux and open source.

SysCast podcast

In the SysCast podcast I talk about Linux & open source projects, interview sysadmins or developers and discuss web-related technologies. A show by and for geeks!

cron.weekly newsletter

A weekly newsletter - delivered every Sunday - for Linux sysadmins and open source users. It helps keeps you informed about open source projects, Linux guides & tutorials and the latest news.

Share this post

Did you like this post? Will you help me share it on social media? Thanks!

Comments

Stéphan Wednesday, April 9, 2014 at 21:40 (permalink)

Too bad there isn’t a master to spin up pools (aka other masters) on demand.

This might be a nice thing for shared hosting.
And would finally put my FPM scripts to good use.

Reply


Chris Thursday, April 10, 2014 at 11:44 (permalink)

Would be nice if the master process doesn’t run as root, but as the pool user. Not sure if that’s possible.

Reply


    Mattias Geniar Thursday, April 10, 2014 at 12:07 (permalink)

    I’m not sure actually. In “normal” PHP-FPM, it would have to be root to suexec the different pools to their user and manage the socket files. If one master will only serve one pool, technically it should be possible to have that master also run as a non-root user.

    From a security POV an improvement, but I would guess a very small improvement as the master itself only spawns children and never actually handles any PHP requests directly.

    Reply


Bart Friday, April 11, 2014 at 05:27 (permalink)

We run masters in ‘chroot userspec’. Works perfectly. IMHO a lot safer than trusting PHP (Php heartbleed anyone?) as root in /. At least I sleep better :).

Reply


    Mattias Geniar Friday, April 11, 2014 at 11:18 (permalink)

    Indeed, that’s a more safe way to run your PHP code. How did you comfortably get around needed libraries within your chroot? Did you hard-link them or copy them into the chroot of each PHP-FPM pool?

    Reply


jerometang Thursday, May 15, 2014 at 08:16 (permalink)

Hi Mattias,

is it common usage for php in enterprise to run multiple apps on one host? with multiple fpm master to manage them.

Reply


    Mattias Geniar Thursday, May 15, 2014 at 09:17 (permalink)

    Hi,

    For larger sites, only one website would be running on one host and this problem indeed does not occur. However, there are _many_ more small sites than there are big sites, and for them this kind of setup would be an improvement for them.

    Reply


Jesin A Tuesday, November 18, 2014 at 14:56 (permalink)

Hi Mattias,

After PHP bug #67060 was fixed how do we configure per-user pools when listen.mode defaults to 0660?

Do we place the user “pool1” in the www-data group?

usermod -G pool1 www-data

Reply


    Mattias Geniar Tuesday, November 18, 2014 at 15:04 (permalink)

    You chown the socket to whichever user is running the Apache service. If your Apache service is running as the “httpd” user, chown the socket as httpd.

    Reply


      Jesin A Tuesday, November 18, 2014 at 15:24 (permalink)

      Wow thanks for the quick reply!

      But when php-fpm is restarted the socket owner is set to pool1 again. So do you suggest something like this:

      listen.owner = httpd
      listen.group = httpd

      user = pool1
      group = pool1

      Or this:

      usermod -aG pool1 httpd

      Reply


      Mattias Geniar Tuesday, November 18, 2014 at 15:25 (permalink)

      Correct, either you set the session owner/group in the PHP-FPM pool config, or you change the socket mode back to 0666 (which is unsecure), to allow everyone read/write access to the socket.

      Reply


      Jesin A Tuesday, November 18, 2014 at 15:44 (permalink)

      What is your opinion on adding the web server user to the “pool1” group?

      usermod -aG pool1 www-data

      The ISPConfig control sets listen.owner/group and user/group to the same username and does this:

      # id www-data

      uid=33(www-data) gid=33(www-data) groups=33(www-data),1003(ispapps),1004(ispconfig),1005(client0)

      Reply


      Mattias Geniar Tuesday, November 18, 2014 at 17:52 (permalink)

      The result will be the same, although that would still allow _any_ website to connect to _any_ PHP-FPM pool socket. So you’re bypassing the security of the bugfix altogether, you might as well go back to the default mode of 0666.

      If you want a secure PHP-FPM config, each PHP-FPM pool needs to have a config that only allows the website user to connect to it, and not every user.

      Reply


      Jesin A Tuesday, November 18, 2014 at 20:52 (permalink)

      Oops I said the opposite of what I meant. We are NOT adding the user (pool1) to the webserver group (www-data) we’re doing the opposite here – adding www-data to the secondary group pool1 or pool2.

      So the webserver user has access to all the sockets but the pool users cannot access each other’s socket. Am I correct here?

      Also why did this article and other articles on PHP-FPM on the web modify listen.user/listen.group options when it is enough to change just user/group even before this bug was identified?

      Reply


John Friday, January 2, 2015 at 03:49 (permalink)

Thanks for the ‘ondemand’ thing! I was looking for a way to create a new php-fpm pool in a new user and group when I ran into your post. I’ve got a fairly quiet server, so ondemand frees up a bunch of mostly unused resources.

Your guide was also helpful in pointing me in the right direction to set up an isolated pool.

Reply


Juraj Simon Tuesday, January 13, 2015 at 12:26 (permalink)

Can you estimate CPU/memory overheat for many (hundreds or thousands) pools when every pool must also have its master process?
One one server I have cca 1000 pools and one master process…
Now this master process now use about ~170MB ram and every child process about ~12MB ram… Did you tested it on something with that many pools?

Reply


    Mattias Geniar Tuesday, January 13, 2015 at 18:28 (permalink)

    Hi Juraj,

    I don’t have any testing I’m afraid, this would have to be done by yourself. It’ll also depend on the PHP version, the amount of requests your applications are getting, which extensions are loaded, …

    For 1.000+ pools I probably wouldn’t look into this. Unless you can go with a systemd approach with socket activation, then the master processes are only started when they’re needed and they don’t consume memory if no requests are coming in.

    Reply


Christian Sunday, February 22, 2015 at 20:59 (permalink)

the one thing I am still a little confused on is when creating all of these other master processes how are you coorelating them to other “sites” or when a certain site is loaded these just start per that site. I am not seeing the connection here… Therefore if I have 25 sites should I do this 25 x’s. etc.

Reply


Andrew Cotton Monday, April 13, 2015 at 19:12 (permalink)

Mattias,

Wow, thanks a lot! I’m actually new to nginx+php-fpm and this has pointed me in the direction I believe we want to go. We have been sharing a pair of application servers for 5 applications developed in house. We have grown and have divided labors a bit and have been using fabric (python library) to aid in updates. There is still some evolution necessary here, but is fine for now.

The great part about this is that I have moved our company towards nginx+php-fpm and believe that, if configured as you suggest, we will be able to have multiple developers updating code in production and restarting master php-fpm processes for a specific pool without affecting the other applications. Basically, an update to app1 will not necessitate an apache restart which used to temporarily cause a glitch in service (even though I used graceful; I never understood why graceful was so clumsy–perhaps it was the opcache being refreshed took a moment?).

Thanks for sharing this and helping us improve our process and gain more control over our deployments and improve our availability.

Reply


simon Thursday, June 11, 2015 at 02:18 (permalink)

Thanks Matthias

The solution you proposed, make my web-site start back to work.
I have a little server on debian with apache 2.4.10 + php5.6

I don’t know why but now with the new apache only one pid shared give me the mess between my pools, and only one web-site was succeded to run each time.

Now i have different pid for different pool (like you proposed) and my php-fpm rocks at last!

And the on demand seems works good.

thks you

Nota :
I have still one problem i cannot access to my root website
i can access to http://www.skiscool.com or fr.skiscool.com but not for http://skiscool.com

And i cannot resolve that i’ve got a 404 not found each time.. i think my host file is good and my bind as well ..
i put in ServerName skiscool.com , and ServerAlias http://www.skiscool.com

but apache still dont want to run the skiscool.com..

Reply


    simon Thursday, June 11, 2015 at 02:30 (permalink)

    sorry i found my bug, i just had to add “rewritebase /” in my htaccess for my problem
    Now it’s working

    Reply


Olaf Lederer Friday, July 24, 2015 at 11:54 (permalink)

Hoi Mattias,

thanks for this great article about how to use on-demand process manager! I have a question about the number of child processes. Based on a low traffic website how many processes should I use for a “normal” WordPress website? Any experience?

Reply


Mattias Geniar Friday, July 24, 2015 at 12:05 (permalink)

Hi Olaf,

For small sites, my rule of thumb of 3-5 PHP processes. It usually only needs one, but there are some plugins that fire of several AJAX calls at once, and they all require PHP processing.

So to have a bit of “concurrency” in the amount of simultaneous requests, I usually keep it at 3-5 processes.

Reply


Richard Wednesday, August 12, 2015 at 00:51 (permalink)

Thanks for the article.
I’ve chopped mysql and php5.6 fpm files on my digitalocean/serverpilot server and ram usage shrinked a lot.
The site doesn’t get many visitors, but it was eating a lot of ram on zero load.
It still needs a lot of configuration, I fear that a 64 OS wasn’t a good choice a 1GB server.

Reply


Nik Saturday, December 26, 2015 at 01:27 (permalink)

hi :)

i am testing your tips with apache2.4, PHP 5.6.16-2 , Zend Engine v2.6.0, Zend OPcache v7.0.6-dev, php5-fpm. I am trying to figure out how can i define different opcache configs following your instructions. It is fine until the definitions of different master processes, one for every pool. The opcache is indeed separated and not shared any more.

but is there a way to config different cache sizes for example? The php.ini file is still one

thanks :)

Reply


Gabriel Tuesday, January 5, 2016 at 22:12 (permalink)

In a dedicated server I suggest to cofigure PHP-FPM with STATIC process manager: just start all possible child processes allowed by the available memory.

Reply


David Levy Sunday, May 22, 2016 at 00:16 (permalink)

I followed your tutorial and after making all the changes, /etc/init.d/php5-fpm-example will not start or report status or anything of that nature, what am I missing?? or how do I troubleshoot this without new logs being created as it’s supposed to?

Reply


Adam Monday, August 1, 2016 at 00:18 (permalink)

Small correction (update?) regarding: “You may not be aware that the APC or OPcache is actually held by the master process in PHP. Any configuration for APC needs to come from the .INI configurations and cannot be overwritten later on via ini_set() or php_admin_value.”

No idea about APC, but in OPcache you can set almost everything via php_admin_value in the FPM pool config, and some options can be set also via ini_set() — see http://php.net/manual/en/opcache.configuration.php

Reply


bdraco Wednesday, September 14, 2016 at 08:02 (permalink)

Ondemand is not recommended until this bug is fixed.

https://bugs.php.net/bug.php?id=69724

Reply


olivedev Friday, November 25, 2016 at 15:53 (permalink)

In dedicated server, if you are using a platform like this one (https://www.cloudways.com/en/php-cloud-hosting.php ), you won’t have to manually install and configure PHP-FPM. This is because these packages are already installed and optimized. What would you recommend?

Reply


cherri3a Sunday, February 12, 2017 at 20:01 (permalink)

first thank’s but wheni change all configuration like this the server can’t save lot of users in the same time

Reply


Cloudnine Wednesday, March 22, 2017 at 09:53 (permalink)

Do you see the blacklist option in Opcache as a way to ensure that certain sites on the server don’t get to share the cache? Many of my clients use the same server for their dev and production sites to keep the costs down and I use the blacklist option to make sure that the cache is used only for the production site.

Reply


jeff caar Saturday, April 1, 2017 at 12:33 (permalink)

Hey Mattias :)

“If you’re working on a high performance PHP setup, the ‘ondemand’ PM may not be for you.”

Considering the typical WordPress site, let’s say running a LEMP stack on a dedicated VPS server (not sharing the server with any other sites), would “ondemand” still make sense, or “dynamic”, assuming that the site is getting at least several hundred or few thousand page loads or visitors each day? Thanks!

Reply


    Per Monday, August 7, 2017 at 11:05 (permalink)

    Yes, I manage quite a few websites both Drubal and WP, most of them get’s about 400K to 800K visitors per day and on demand is definitely still worth running.

    Reply


      Mattias Geniar Monday, August 7, 2017 at 16:59 (permalink)

      Keep in mind though that with that many visitors, it’ll behave more like “dynamic” than “ondemand”. Ondemand implies that, with 0 visitors, the child processes get killed and it won’t consume any memory. With sufficient visitors, you’ll always have _some_ child processes active.

      The bottleneck with ondemand is the potentially slow startup of child processes on the first hit, so as long as you keep that process/cache “hot”, there is hardly any difference between dynamic vs. ondemand.

      Reply


Leave a Reply to Richard Cancel reply

Your email address will not be published. Required fields are marked *

Inbound links