PHP-FPM¶
Installing PHP-FPM¶
Add the PPA:
sudo apt update && sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update && sudo apt install php7.4 php7.4-fpm php7.4-cli
Amend the php.ini file:
sudo nano /etc/php/7.4/cli/php.ini
Change the following global settings for PHP:
date.timezone = "Europe/London"
expose_php = Off
display_errors = off
log_errors = on
file_uploads = On
upload_max_filesize = 2M
post_max_size = 2M
max_execution_time = 300
allow_url_fopen = off
Enable the fpm service:
sudo systemctl enable php7.4-fpm --now
Disable Mod_php and mpm_prefork, and enable mpm_event (mpm_event should be enabled by default but we'll do this just to make sure):
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
Enable modules required for PHP-FPM:
sudo a2enmod alias proxy_fcgi setenvif
Enable PHP-FPM configuration:
sudo a2enconf php7.4-fpm
Add the required entries to the Virtualhost config:
sudo nano /etc/apache2/sites-available/example.com.conf
<VirtualHost 127.0.0.1:8080>
...
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php/php7.4-fpm.sock|fcgi://localhost"
</FilesMatch>
...
</VirtualHost>
Restart Apache2:
sudo systemctl restart apache2
PHP Pools¶
The default PHP-FPM pool configuration is found at /etc/php/7.4/fpm/pool.d/www.conf
Unless further amendments are made, all PHP sites created will use this single pool. The pool configuration file contains the following 4 settings:
; pool name
[www]
...
; Unix user/group of processes
; will be used.
user = www-data
group = www-data
...
; Socket to which the Apache will connect
listen = /run/php/php7.4-fpm.sock
For example, let’s create another pool called example to be used with our example.com website. Make sure that beforehand you create the user and group that will be used by the new pool. Create them with the commands:
sudo groupadd example
sudo useradd -g example example
Create a temp directory in your app folder:
mkdir -p /var/www/example.com/html/tmp
chown -R example:example /var/www/example.com/html/tmp
Now copy the pool file www.conf to example.conf:
sudo cp /etc/php/7.4/fpm/pool.d/www.conf /etc/php/7.4/fpm/pool.d/example.conf
Now edit the new file and amend as follows:
; pool name
[example]
...
; Unix user/group of processes
; will be used.
user = example
group = example
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
...
; Socket to which the Apache will connect
listen = /run/php/php7.4-fpm-example.sock
; Restrict PHP scripts to app folder
; Extra folder paths can be added, seperated with a colon
; Or the restriction can be removed by specifying none
php_admin_value[open_basedir] = /var/www/example.com/html
php_admin_value[sys_temp_dir] = /var/www/example.com/html/tmp
php_admin_value[upload_tmp_dir] = /var/www/example.com/html/tmp
;
; Some php settings can be set per pool, some examples are below.
; Uncomment and amend as appropriate
;
;php_admin_value[memory_limit] = 512M
;php_admin_flag[output_buffering] = Off
;php_admin_value[max_execution_time] = 1800
;php_admin_value[max_input_time] = 3600
;php_admin_value[post_max_size] = 2M
;php_admin_value[upload_max_filesize] = 2M
;php_admin_value[max_file_uploads] = 100
;php_admin_flag[session.cookie_secure] = True
;
; Disable unneeded and potentially dangerous functions, eg:
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,dl,setenv
And finally restart the PHP-FPM service:
sudo service php7.4-fpm restart
The Apache virtualhost should also be edited to include the new socket used by the new pool:
sudo nano /etc/apache2/sites-available/example.com.conf
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php/php7.4-fpm-example.sock|fcgi://localhost/"
</FilesMatch>
Now change recursively the ownership of the Virtualhost directory:
sudo chown -R example:www-data /var/www/example.com
And set the permissions:
sudo chmod -R 740 /var/www/example.com
Check your websites documentation. Some folders may need to be writable by the web server. If so then change them like:
sudo chmod -R 770 /var/www/example.com/html/writable-folder
PHP files can be further restricted like so:
find /var/www/example.com/html -type f -name '*.php' -exec chmod 600 {} \;
Tuning Apache2 and PHP-FPM¶
This involves a bit of calculating.
First we install the py_mem utility:
sudo -s
python3 -m pip install py_mem
The first thing we need to know is how many cores we have available for use:
lscpu | egrep 'Model name|Socket|Thread|NUMA|CPU\(s\)'
Sample output:
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Socket(s): 1
NUMA node(s): 1
Model name: Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
NUMA node0 CPU(s): 0-3
Here we can see that there are 4 physical cores and 2 threads per core, so we class this as 8 logical cores.
Now we find the total available RAM:
vmstat -s -S M | grep "total memory"
Sample output:
31854 M total memory
We need to make a decision on how much of this RAM to allocate in total to PHP-FPM processes. If you have other services running such as a mail server, database, etc, then you'll want to keep a fair amount in reserve. For this example, we'll allocate 16 GB of RAM to PHP-FPM.
Now let's say we have 2 large PHP apps being served, each with their own dedicated PHP Pool. We'll call these app1 and app2. For this example, let's allocate 10 GB of the 16 GB to app1 and the remaining 6 GB to app2.
We'll now find out how many processes Apache2 is running, and how much memory it's using per process:
ps_mem | grep 'apache2'
This will output something like:
10.5 MiB + 40.2 MiB = 50.8 MiB apache2 (8)
So here we see that Apache2 is using a total of 50.8 MB of memory over 8 processes
Now we'll check the usage of the app1 and app2 pools. We'll assume that the users of the pools are called www-app1 and www-app2:
ps_mem -p $(pgrep -d, -u www-app1) | grep 'php-fpm7.4'
ps_mem -p $(pgrep -d, -u www-app2) | grep 'php-fpm7.4'
Sample output:
371.5 MiB + 456.0 MiB = 827.5 MiB php-fpm7.4 (32)
256.6 MiB + 383.3 MiB = 639.9 MiB php-fpm7.4 (32)
So app1 is using 827.5 MB over 32 processes, and app2 is using 639.9 MB over 32 processes.
We'll use all these values to set the values in the mpm_event.conf and php-fpm pool config.
First we'll do the Apache mod_mpm_event settings. Here are the calculations:
ServerLimit = MaxRequestWorkers / ThreadsPerChild
StartServers = Number of cores (in our case, 8)
ThreadsPerChild = 25
MaxRequestWorkers = Total allocated memory / average memory per process
(rounded up to the nearest multiple of ThreadsPerChild)
MinSpareThreads = MaxRequestWorkers / 2
MaxSpareThreads = MaxRequestWorkers
So in our examples, this would work out as:
ServerLimit 104
StartServers 8
ThreadsPerChild 25
MaxRequestWorkers 2600
MinSpareThreads 1300
MaxSpareThreads 2600
Now we'll do the pool settings for app1 and app2.
The setting we tune are:
pm.max_children = Total RAM allocated to pool / average memory per pool process
pm.start_servers = Number of cores x 4
pm.min_spare_servers = Number of cores x 2
pm.max_spare_servers = Number of cores x 4
So in our examples, this work work out as:
[app1]
pm.max_children = 396
pm.start_servers = 32
pm.min_spare_servers = 16
pm.max_spare_servers = 32
[app2]
pm.max_children = 308
pm.start_servers = 32
pm.min_spare_servers = 16
pm.max_spare_servers = 32
Restart php-fpm and apache2 when finished tuning:
sudo systemctl restart apache2 && systemctl restart php7.4-fpm