11.12.15

Installing PHP 7.0.0 from scratch on CentOS 7.1


PHP 7.0.0 came out in early December. Since I've been blathering on about building bleeding edge web server software, let's take it for a run. My favourite distro, CentOS 7.1, doesn't have it available in packages yet, so let's build it from sources!


Prerequisites


First, let's assume that you've gone ahead and got (Apache's httpd)[http://shaniber.blogspot.ca/2015/12/installing-apache-httpd-with-http2.html] installed. We did that earlier (link to last blog post) when we set up httpd with HTTP/2 support. If you haven't done that yet, go do it. It's important.


We need some softwares to compile PHP 7 against, so install them first:


yum install -y mysql-devel libxml2-devel bzip2-devel curl-devel libjpeg-devel libpng-devel freetype-devel libc-client-devel libmcrypt-devel sqlite-devel libedit-devel net-snmp-devel systemd-devel mariadb mariadb-server mariadb-devel libxslt-devel

Installation


Now grab the archive, extract it, and get to work compiling it.


wget http://ca3.php.net/distributions/php-7.0.0.tar.gz && tar xf php-7.0.0.tar.gz
cd php-7.0.0
./configure --prefix=/usr/local \
        --sysconfdir=/etc \
        --localstatedir=/var \
        --enable-fpm \
        --with-fpm-systemd \
        --with-fpm-user=php \
        --with-fpm-group=www \
        --with-config-file-path=/etc \
        --with-config-file-scan-dir=/etc/php.d \
        --with-libxml-dir=/usr \
        --with-openssl=/usr/local/openssl-1.0.2d \
        --with-pcre-regex \
        --with-zlib \
        --enable-bcmath \
        --with-bz2=/usr \
        --enable-calendar \
        --with-curl=/usr \
        --enable-exif \
        --with-gd \
        --enable-mbstring \
        --with-mcrypt \
        --with-jpeg-dir=/usr \
        --with-png-dir=/usr \
        --enable-zip \
        --enable-ftp \
        --with-kerberos \
        --with-gettext \
        --with-xmlrpc \
        --with-xsl \
        --enable-opcache \
        --enable-mysqlnd \
        --with-pear 
make
make test
make install

Configuration


We'll set up php the new-fangled way, using FastCGI Process Manager, aka PHP-FPM. I mean, we've got that (server set up with HTTP/2)[http://shaniber.blogspot.ca/2015/12/installing-apache-httpd-with-http2.html] already, and we've got bleeding edge PHP 7.0.0, so let's go all the way, right? (Not really new fangled, PHP-FPM has been around for a while, but let's be honest, mod_php is generally how it's done.)


We defined a 'php' user during the configuration step for PHP to run as, so we should also create it:


useradd -g www -m php

This is just a basic set up, no VirtualHosts, everything right in the main httpd config, with a single pool of FastCGI processes wfrom which to serve PHP.


Set up PHP-FPM


In order to have httpd access PHP, we need to define a PHP-FPM worker. Based on our configuration above, we should find a default configuration file and directory in /etc. Here's a stripped down version of php-fpm.conf that works for me. Edit your file to match your environment. In this case, the php-fpm.log will end up /usr/local/log, so make sure that it exists, as well.


/etc/php-fpm.conf:
;; Global Options
[global]
pid = /var/run/php-fpm.pid
error_log = log/php-fpm.log
log_level = debug

;; Pool Definitions
include=/etc/php-fpm.d/*.conf

Now we need to set up the worker pool. There's varying opinions on the best way to do this, but we're just going to go with a basic dynamic set up for the moment. We can set up multiple pools to serve for different domains, and each must have a unique name, and must listen on a unique port. This will spawn several worker children once it's started up to handle requests.


cd /etc/php-fpm.d
cp www.conf.default example_com.conf

/etc/php-fpm.d/example_com.conf:
[example_com]
user = php
group = www
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
access.log = log/$pool.access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
php_admin_value[error_log] = /var/log/fpm-php.$pool.log
php_admin_flag[log_errors] = on

php.ini configuration


Based on our configuration above, PHP will look for its main configuration file in /etc, and supplemental configuration files in /etc/php.d. Copy one of the versions (development or production) from .../{src}/php-7.0.0/. I'm using the development version. If you're installing this on a production machine, then use the production version.


cd /usr/local/src/php-7.0.0
cp php.ini-development /etc/php.ini

The php.ini file is huge, with numerous configuration settings. We'll just change a couple of settings to make it a little more useful, including setting a timezone, changing some memory limits, making the installation more secure, and enabling OPCache. Remember that if you modify ini settings in your pool config (like we did above), they'll override anything you set here.


/etc/php.ini:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
expose_php = Off
max_execution_time = 30
memory_limit = 64M
date.timezone = UTC
error_reporting = E_ALL & ~E_DEPRECATED
post_max_size = 5M
upload_max_filesize = 4M

opcache.enable=1
opcache.memory_consumption=64
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=7000
opcache.validate_timestamps=0 ;set this to 1 on production server
opcache.fast_shutdown=1

systemd configuration


Define a service that runs when systemd starts up. This file exists in your src directory, in php-7.0.0/sapi/fpm/php-fpm.service, but needs to be modified for your environment. (It feels like this should be installed automagically, but it wasn't for me). Copy or create it in /etc/systemd/system.


/etc/systemd/system/php-fpm.service:
[Unit]
Description=The PHP 7 FastCGI Process Manager
After=network.target

[Service]
Type=simple
PIDFile=/var/run/php-fpm.pid
ExecStart=/usr/local/sbin/php-fpm --nodaemonize --fpm-config /etc/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

Now enable and start PHP-FPM.


systemctl enable php-fpm.service
systemctl start php-fpm.service

If you ask systemctl for the status of php-fpm, you should see a master process, and two pool processes for pool example_com, if you've been following along here.


Set up httpd access


Assuming you've gotten to this point, and everything seems to be working, we can now add PHP-FPM support to Apache httpd. We're proxying all files with the .php extention to the workers in the PHP-FPM pool we defined earlier.


First, make sure that the appropriate modules are enabled in /etc/httpd/conf/httpd.conf, and we include the php-fpm.conf file that we'll create later:


LoadModule proxy_module /usr/local/libexec/mod_proxy.so
LoadModule proxy_fcgi_module /usr/local/libexec/mod_proxy_fcgi.so

...

Include conf/extra/php-fpm.conf

Since we're just doing a very basic set up, and no VirtualHosts exist, we will just add the PHP-FPM configuration to the main server, in an extra config file.


/etc/httpd/conf/extra/php-fpm.conf:
<LocationMatch "^/(.*\.php(/.*)?)$">
  ProxyPass fcgi://127.0.0.1:9000/var/www/html/$1
</LocationMatch>

Make sure that the port (9000, here) matches the port you set above in your pool config file. Restart httpd, then add a simple file in your document root (which we set to /var/www/html in our last adventure):


/var/www/html/phpinfo.php:
<?php
    phpinfo();
?>

Now, hit the url for your server and load the file, and see if everything is awesome! If you see the usual PHP Info page, then you can celebrate your Great Success!


Remember now, though, that if you make a change to your PHP configuration in the php.ini file, or if you change your PHP-FPM set up, you'll need to reload your pool.


So what's next? Right now, we've got a pretty basic LAMP stack set up. We should probably abstract this out so that we can apply it to different VirtualHosts. We can also set up PHP-FPM with separate ''ondemand'' master pools for each VHost, for improved security. It would also be pretty nifty to make PHP-FPM communicate via systemd sockets, too.