You've set up a new server, installed PHP above your database of choice, and your site has been running happily in the background. But now your SEO work and recent marketing campaign is funnelling more traffic to your server. You are at the stage of business growth where having a stable website is vital and your online popularity is having the opposite effect. Visitors to your website will experience either a 'This website took too long to respond' message or, worse, an Error 500 (internal server error).

If your website is struggling to support extra traffic then speak to us about the technical support we can provide. We offer competitively priced hosting packages to suit any business - click here to learn more.

We build it. We host it. We keep it safe.

Apache mod_php

By default, Apache serves PHP pages using mod_php. This in itself is not necessarily a bad thing. Essentially Apache will spawn ('pre-fork') a new process for each connection starting with, and keeping, a pool of processes available for any page being served. Each spawned process is 'self contained', meaning it contains its own copy of the PHP interpreter. On the one hand this results in efficient serving of 'PHP heavy' websites (WordPress and Moodle are good examples) but, on the other, each spawned process can consume a good deal of memory. As your site becomes popular (and again we're thinking here of both wanted and unwanted visits) you'll see memory use climb. It is possible to tune mod_php to ensure Apache doesn't attempt to consume more memory than you have available and we'll study this is a subsequent post. But for now let's take a look at php-fpm.

PHP-FPM

For our more popular PHP-based sites we employ PHP-FPM - the FastCGI Process Manager - to serve PHP files. How does this work? When Apache receives a request for a .php file it passes this on to the (separate) PHP-FPM service. PHP-FPM then processes the request and hands it back to Apache to serve. PHP-FPM maintains pools of worker threads to achieve more efficient processing.

Tuning PHP-FPM

Leave your server running for a couple of days and check the PHP-FPM log. Run the following command (note that we're currently running PHP 7.3 and the '7.3' in 'php7.3' in the examples below may need to be replaced with the version number of PHP you're running):

# sudo nano /var/log/php7.3-fpm.log

You might see the following:

[05-Feb-2020 08:28:31] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
[05-Feb-2020 08:28:31] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 9 total children

Our server was beginning to get busy just as the working day starts - which is expected - and so clearly we need to don our overalls and open our server toolbox.

Doing the Math

Run the following command:

# ps -ylC php-fpm7.3 --sort:rss

This will print something similar to:

S UID PID  PPID C PRI NI RSS    SZ     WCHAN TTY TIME CMD
S 0   9030 1    0 80  0  31244  120002 -     ?   00:00:05 php-fpm7.3
S 33  7511 9030 1 80  0  130636 148827 -     ?   00:00:04 php-fpm7.3
S 33  7479 9030 2 80  0  133724 150336 -     ?   00:00:16 php-fpm7.3
S 33  7504 9030 2 80  0  136272 150365 -     ?   00:00:08 php-fpm7.3

Looking at these figures we're seeing roughly 150000KB, or 0.15GB, given to each php-fpm7.3 process.

Next we need to estimate how much RAM is being consumed by other processes. Run:

# top

Press the '<' key three times to sort by RES. The output may look something like the following (this is an extract):

PID   USER     PR NI VIRT    RES    SHR   S %CPU %MEM  TIME+  COMMAND
1114  mysql    20 0  1624004 296388 18780 S 0.0  7.3 16:36.53 mysqld
8656  www-data 20 0  604176  148324 96236 S 0.0  3.7 0:14.98 php-fpm7.3
8840  www-data 20 0  603624  146836 94808 S 0.0  3.6 0:12.41 php-fpm7.3
8474  www-data 20 0  604004  146308 94884 S 0.0  3.6 0:24.84 php-fpm7.3
416   root     19 -1 153176  65056  62980 S 0.0  1.6 0:07.50 systemd-journal
9111  root     20 0  539516  35168  27912 S 0.0  0.9 0:09.57 apache2
9030  root     20 0  480008  31244  25436 S 0.0  0.8 0:06.25 php-fpm7.3
11852 root     20 0  1237268 24372  10108 S 0.3  0.6 5:50.86 fail2ban-server
8300  www-data 20 0  542652  21868  13880 S 0.0  0.5 0:00.54 apache2
8086  www-data 20 0  542636  21832  13868 S 0.0  0.5 0:00.71 apache2
8424  www-data 20 0  542600  21804  13876 S 0.0  0.5 0:00.37 apache2
8683  www-data 20 0  542600  21804  13880 S 0.0  0.5 0:00.27 apache2
8594  www-data 20 0  542584  21796  13884 S 0.0  0.5 0:00.31 apache2
8444  www-data 20 0  542592  21784  13860 S 0.0  0.5 0:00.38 apache2
8600  www-data 20 0  542576  21756  13852 S 0.0  0.5 0:00.24 apache2
8943  www-data 20 0  542596  21720  13808 S 0.0  0.5 0:00.11 apache2
8951  www-data 20 0  542520  21520  13720 S 0.0  0.5 0:00.01 apache2
8944  www-data 20 0  542584  20896  13020 S 0.0  0.5 0:00.06 apache2
1014  root     20 0  187680  20088  12172 S 0.0  0.5 0:00.12 unattended-upgr
25696 www-data 20 0  173904  9200   4232  S 0.0  0.2 0:00.37 apache2

From this data we are looking at needing to reserve at least 300,000KB, or 0.3GB to MySQL (and obviously better to allocate more). We are also spawning Apache processes through mod_php and the maximum number of these (at approx. 14000KB per instance) will need to be limited and taken account of (we'll take a look at this in a separate post).

For now, let us allocate 2GB to PHP-FPM. At 0.15GB per process that's 2 divided by 0.15, giving space for just over 13 child processes. Let's make it a maximum of 12.

Configuring PHP-FPM

To configure PHP-FPM run the following command

# sudo nano /etc/php/7.x/fpm/pool.d/www.conf

The key setting to look for are:

pm = dynamic

We could consider setting this to 'static' but, as we have other services running on this server (namely mysqld and Apache forks) we are going to leave it as dynamic and let it flex.

And, based on how many php-fpm7.x processes seem to be running throughout the working day, we are going to specify the following:

pm.max_children = 12 
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.start_servers = 12

We are also aware of the potential for memory leaks in the WordPress plugins employed on this particular site so we are going to set:

pm.max_requests = 500

Save the file and then restart PHP-FPM:

# sudo service php7.3-fpm restart

Continue to carefully monitor memory use.

 

Leave a Comment