Another approach to configure Apache + PHP is to use the new PHP-FPM (FPM stands for FastCGI Process Manager), it manages PHP processes spawn in its own server (outside Apache) allowing you some cool things:
- Better process control (min, max process number and spare processes control)
- Better management for PHP plugins like APC versus mod_php
- chrooting, so we can isolate applications or virtual hosts
- Run processes with specific uid/gid (this could be done with suExec apache mod)
- Better performance, in my tests, it runs 10% faster than CGI
We can’t use mod_fcgid (as we did with the CGI approach), because we can’t connect to any external FastCGI server, so we will use mod_fastcgi (a bit older but useful). In Apache 2.4 we could use the new mod_proxy_fcgi.
Installing mod_fastcgi (Apache 2.2) and php-fpm
If using a Debian/Ubuntu like distribution:
$ sudo apt-get install php5-fpm libapache2-mod-fastcgi
Configuring Apache 2.2
The first thing is enabling the module:
$ sudo a2enmod fastcgi
You must also enable actions module (if not enabled yet):
$ sudo a2enmod actions
We have to create a php-FPM configuration file for Apache, like this (/etc/apache2/conf.d/php-fpm.conf):
<IfModule mod_fastcgi.c> Alias /php5-fpm-cgi /usr/lib/cgi-bin/php5-fcgi FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -socket /var/run/php-fpm.sock -pass-header Authorization </IfModule>
Now, lets edit our VirtualHost configuration (I picked the example found at Installing and configuring web server, and put in bold the changes):
<VirtualHost *:80> ServerAdmin email@example.com ServerName totaki.com ServerAlias www.totaki.com DocumentRoot /home/cloud/www/totaki.com/www <Directory /> Options FollowSymLinks AllowOverride All </Directory> <Directory /home/cloud/www/totaki.com/www/> Options FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory> <IfModule mod_fastcgi.c> <FilesMatch ".+\.ph(p?|t|tml)$"> SetHandler application/x-httpd-php </FilesMatch> Action application/x-httpd-php /php5-fpm-cgi </IfModule> ErrorLog "/home/cloud/www/totaki.com/logs/error.log" LogLevel warn CustomLog "/home/cloud/www/totaki.com/logs/access.log" combined </VirtualHost>
It can also be done to affect the whole server putting a new file at /etc/apache2/conf.d/ but I prefer doing it by VirtualHost to get more control.
We will use cgi server at socket /var/run/php-ftm.sock (we can configure a remote server, to distribute the load, preferably in the same datacenter, we could access by LAN, so it will be faster), but when configuring on the same
machine it will run fine as UNIX socket.
Configuring Apache 2.4
In this newer version of Apache, sometimes mod_fcgi is not available to install in our servers, so we must use another module like mod_proxy_fcgi. It gives us a lot of new cool stuff we can use.
In versions prior to Apache 2.4.4 we can only use it with PHP-FPM listening on a port (not an unix socket). We can edit our VirtualHost like this:
<VirtualHost *:80> ServerAdmin firstname.lastname@example.org ServerName totaki.com ServerAlias www.totaki.com DocumentRoot /home/cloud/www/totaki.com/www ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/home/cloud/www/totaki.com/www/$1 <Directory /> Options FollowSymLinks AllowOverride All </Directory> <Directory /home/cloud/www/totaki.com/www/> Options FollowSymLinks MultiViews AllowOverride All Require all grantes </Directory> ErrorLog "/home/cloud/www/totaki.com/logs/error.log" LogLevel warn CustomLog "/home/cloud/www/totaki.com/logs/access.log" combined </VirtualHost>
And that’s it. The worst is that we must specify the full path in every virtualhost. But there are some great things we can do like using a load balancer por that proxy, or making a global configuration in the latest versions. More information here.
We must edit /etc/php5/fpm/pool.d/www.conf to change some settings. Server settings for processes number may depend on the CPU+RAM on your server, so you can tune it up later.
The file more or less looks like (put in bold the changes):
; Start a new pool named 'www'. ; the variable $pool can we used in any directive and will be replaced by the ; pool name ('www' here) [www] ; Per pool prefix ; It only applies on the following directives: ; - 'slowlog' ; - 'listen' (unixsocket) ; - 'chroot' ; - 'chdir' ; - 'php_values' ; - 'php_admin_values' ; When not set, the global prefix (or /usr) applies instead. ; Note: This directive can also be relative to the global prefix. ; Default Value: none ;prefix = /path/to/pools/$pool ; Unix user/group of processes ; Note: The user is mandatory. If the group is not set, the default user's group ; will be used. user = www-data group = www-data ; The address on which to accept FastCGI requests. ; Valid syntaxes are: ; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on ; a specific port; ; 'port' - to listen on a TCP socket to all addresses on a ; specific port; ; '/path/to/unix/socket' - to listen on a unix socket. ; Note: This value is mandatory. ;listen = 127.0.0.1:9000 listen = /var/run/php-fpm.sock ; Set listen(2) backlog. A value of '-1' means unlimited. ; Default Value: 128 (-1 on FreeBSD and OpenBSD) ;listen.backlog = -1 ; Set permissions for unix socket, if one is used. In Linux, read/write ; permissions must be set in order to allow connections from a web server. Many ; BSD-derived systems allow connections regardless of permissions. ; Default Values: user and group are set as the running user ; mode is set to 0666 listen.owner = www-data listen.group = www-data ;listen.mode = 0666 ; List of ipv4 addresses of FastCGI clients which are allowed to connect. ; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original ; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address ; must be separated by a comma. If this value is left blank, connections will be ; accepted from any ip address. ; Default Value: any ;listen.allowed_clients = 127.0.0.1 ; Choose how the process manager will control the number of child processes. ; Possible Values: ; static - a fixed number (pm.max_children) of child processes; ; dynamic - the number of child processes are set dynamically based on the ; following directives. With this process management, there will be ; always at least 1 children. ; pm.max_children - the maximum number of children that can ; be alive at the same time. ; pm.start_servers - the number of children created on startup. ; pm.min_spare_servers - the minimum number of children in 'idle' ; state (waiting to process). If the number ; of 'idle' processes is less than this ; number then some children will be created. ; pm.max_spare_servers - the maximum number of children in 'idle' ; state (waiting to process). If the number ; of 'idle' processes is greater than this ; number then some children will be killed. ; ondemand - no children are created at startup. Children will be forked when ; new requests will connect. The following parameter are used: ; pm.max_children - the maximum number of children that ; can be alive at the same time. ; pm.process_idle_timeout - The number of seconds after which ; an idle process will be killed. ; Note: This value is mandatory. pm = dynamic ; The number of child processes to be created when pm is set to 'static' and the ; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. ; This value sets the limit on the number of simultaneous requests that will be ; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. ; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP ; CGI. The below defaults are based on a server without much resources. Don't ; forget to tweak pm.* to fit your needs. ; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' ; Note: This value is mandatory. pm.max_children = 50 ; The number of child processes created on startup. ; Note: Used only when pm is set to 'dynamic' ; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 pm.start_servers = 30 ; The desired minimum number of idle server processes. ; Note: Used only when pm is set to 'dynamic' ; Note: Mandatory when pm is set to 'dynamic' pm.min_spare_servers = 20 ; The desired maximum number of idle server processes. ; Note: Used only when pm is set to 'dynamic' ; Note: Mandatory when pm is set to 'dynamic' pm.max_spare_servers = 40 ; The number of seconds after which an idle process will be killed. ; Note: Used only when pm is set to 'ondemand' ; Default Value: 10s ;pm.process_idle_timeout = 10s; ; The number of requests each child process should execute before respawning. ; This can be useful to work around memory leaks in 3rd party libraries. For ; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. ; Default Value: 0 pm.max_requests = 10000
… The rest of the file remains unchanged (mine was all commented), but it’s worth saying that you can have a slow-script log showing backtrace of slow scripts, and we can change php.ini settings from this file.
Now, all you have to do is restart php5-fpm daemon:
$ sudo service php5-fpm restart
$ sudo service apache2 restart
Enable FPM ping and status
We can also request ping and status to FPM daemon by adding a few lines. First, in /etc/php5/fpm/pool.d/www.conf, add or uncomment the following lines:
pm.status_path = /status ping.path = /ping ;ping.response = pong
The last line, may be one word as response for a ping signal.
Then, in our VirtualHost file, add the following:
<FilesMatch "^(ping|status)$"> SetHandler application/x-httpd-php Action application/x-httpd-php /php5-fpm-cgi virtual </FilesMatch>
In this case, FilesMatch will only work when the URL finishes with ping or status (can also use LocationMatch “/status” and LocationMatch “/ping” instead. The action line will allow passing the CGI daemon a non-existent file (due to we have set the virtual keyword)
Remember securing this, or people will have access these two URLs, we can use this:
<FilesMatch "^(ping|status)$"> SetHandler application/x-httpd-php Action application/x-httpd-php /php5-fpm-cgi virtual Order deny,allow Deny from all Allow from [my-ip] </FilesMatch>
Where [my-ip] is your public IP address (or maybe 127.0.0.1 for local ping)
Create pools for different php-fpm configurations
If you want to create a new FPM configuration for a different VirtualHost to restrict parameters, change the user or to isolate it, just edit the apache php-fpm configuration (/etc/apache2/conf.d/php-fpm.conf) (using bold for new settings):
<IfModule mod_fastcgi.c> Alias /php5-fpm-cgi /usr/lib/cgi-bin/php5-fcgi FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -socket /var/run/php-fpm.sock -pass-header Authorization Alias /php5-fpm-heavy-cgi /usr/lib/cgi-bin/php5-hfcgi FastCgiExternalServer /usr/lib/cgi-bin/php5-hfcgi -socket /var/run/php-fpm2.sock -pass-header Authorization </IfModule>
In this case, we are creating another Alias called /php5-fpm-heavy-cgi where we will put the configuration for heavy-load sites (spawn less childs, for example), notice I’m using /var/run/php-fpm2.sock, then I’m going to create a new pool configuration, first copying the existent one:
$ sudo cp /etc/php/fpm/pool.d/www.conf /etc/php/fpm/pool.d/heavy_www.conf
and then, edit this file. The most important thing is to change the line with [www] to another thing, as a way to name your new pool, then change the value under listen to /var/run/php-fpm2.sock, then you can alter whatever you want.
The last thing is to use this new configuration in a VirtualHost, just change
Action application/x-httpd-php /php5-fpm-cgi
Action application/x-httpd-php /php5-fpm-heavy-cgi