Description, Purpose & Scope
Often with a VPS, dedicated server or elastic services such as AWS, there’s a need or requirement to be able to install and update new plugins and themes. WordPress requires write privileges to the filesystem through the webserver (Apache httpd / nginx). This requires more than just setting a few permissions on the system, for this we need to change the PHP handler from the stock mod_php (DSO) to something capable of handling these requests. This is where PHP-FPM comes in, it is built into PHP core and alters the way PHP files are handled and processed by the webserver.
What is PHP-FPM? – Features
PHP-FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with some additional features useful for sites of any size, especially busier sites.
- Ability to start workers with different uid/gid/chroot/environment and different php.ini (replaces safe_mode)
- Separate chrooted DocumentRoot per site, protecting sites from one another
- Independent PHP settings per site; upload limits, memory, etc
- Adaptive process spawning (NEW!)
- Basic statistics (ala Apache’s mod_status) (NEW!)
- Advanced process management with graceful stop/start
- Stdout & stderr logging
- Emergency restart in case of accidental opcode cache destruction
- Accelerated upload support
- Support for a “slowlog”
- Enhancements to FastCGI, such as fastcgi_finish_request() – a special function to finish request & flush all data while continuing to do something time-consuming (video converting, stats processing, etc.)
Required Packages & Configuration Files
- php70u-fpm
/etc/php-fpm.d
- example.com vhost config
/etc/httpd/conf.d/example.com.conf
- example.net vhost config
/etc/httpd/conf.d/example.net.conf
- example.org vhost config
/etc/httpd/conf.d/example.org.conf
In this setup we’ll assume the following:
- The server is running Centos, RHEL or Fedora. This information can be adapted to any system running Apache httpd and PHP-FPM, though filenames and locations may be different the core directives and applications are not.
- The server is running php56u or php70u from IUS repo.
- The server has a users and groups ‘webcom’, ‘webnet’, ‘weborg’.
- The domains are SSL/TLS enabled.
- The domains are ‘example.com’, ‘example.net’, ‘example.org’.
- Decide whether you want to use TCP or Unix sockets for your configuration (see options below)
Note: Unix socket method inspired by Jeff Hays (aka jphase).
Doing the work
- Install the required packages, the assumes you’ve installed php70u from IUS repository.
- Create your PHP-FPM users on the system with nologin shells
- PHP-FPM site configuration files
- Apache httpd VirtualHost configuration files:
- Use chown and chmod on the DocumentRoot’s to set their respective users/groups and set standard filesystem permissions
chown -Rf webcom:webcom /var/www/example.com/main/siteroot
chown -Rf weborg:weborg /var/www/example.org/main/siteroot
chown -Rf webnet:webnet /var/www/example.net/main/siteroot
find /var/www/example.com/main/siteroot -type d -exec chmod 755 {} \; && find /var/www/example.com/main/siteroot -type f -exec chmod 644 {} \;
find /var/www/example.org/main/siteroot -type d -exec chmod 755 {} \; && find /var/www/example.org/main/siteroot -type f -exec chmod 644 {} \;
find /var/www/example.net/main/siteroot -type d -exec chmod 755 {} \; && find /var/www/example.net/main/siteroot -type f -exec chmod 644 {} \;
yum install php70u-fpm
These users will never login to the system and are only used for PHP-FPM processes. Do not assign them passwords and follow the securing sshd document to make sure no one can login except those users approved to.
sudo useradd -s /sbin/nologin webcom
sudo useradd -s /sbin/nologin webnet
sudo useradd -s /sbin/nologin weborg
Note: Using Unix sockets over TCP sockets is marginally faster and more secure, as it’s not connecting to the network. However, you may need to adjust your operating system settings to allow for more connections if you start getting errors, or switch to the TCP method.
example.com PHP-FPM configuration | /etc/php-fpm.d/example.com.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | ################# REMOVE THESE LINES #################### ### Pick *one* of these methods, TCP is commented out ### ################# REMOVE THESE LINES #################### [example.com] ; Using TCP Sockets ;listen = 127.0.0.1:9001 ;listen.allowed_clients = 127.0.0.1 ; Using Unix Sockets listen = /var/run/php-fpm/example_com.sock listen.mode = 0600 user = webcom group = webcom pm = ondemand ;pm = dynamic pm.max_children = 4 pm.status_path = /statusfpm slowlog = /var/log/php-fpm/example.com_slow.log catch_workers_output = yes chdir = /var/www/example.com/main/siteroot php_flag[display_errors] = Off php_flag[magic_quotes_gpc] = Off php_flag[track_vars] = On php_flag[register_globals] = Off php_value[include_path] = . php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/mod_php/session ;php_value[session.save_path] = /var/lib/php/fpm/session php_admin_value[max_input_vars] = 1800 php_admin_value[upload_max_filesize] = 18M php_admin_value[post_max_size] = 72M php_admin_value[expose_php] = Off php_admin_value[error_log] = /var/log/php-fpm/example.com_php-fpm.log php_admin_flag[log_errors] = On php_admin_flag[allow_url_fopen] = Off php_admin_value[upload_tmp_dir] = /tmp |
»»» example.com Unix socket configuration «««
touch /var/run/php-fpm/example_com.sock
chown root:root /var/run/php-fpm/example_com.sock
chmod 600 /var/run/php-fpm/example_com.sock
or in one go with Python:
python -c "import socket as s; sock = s.socket(s.AF_UNIX); sock.bind('/run/php-fpm/example_com.sock')"
example.net PHP-FPM configuration | /etc/php-fpm.d/example.net.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | ################# REMOVE THESE LINES #################### ### Pick *one* of these methods, TCP is commented out ### ################# REMOVE THESE LINES #################### [example.net] ; Using TCP Sockets ;listen = 127.0.0.1:9002 ;listen.allowed_clients = 127.0.0.1 ; Using Unix Sockets listen = /var/run/php-fpm/example_net.sock listen.mode = 0600 user = webnet group = webnet pm = ondemand ;pm = dynamic pm.max_children = 4 pm.status_path = /statusfpm slowlog = /var/log/php-fpm/example.net_slow.log catch_workers_output = yes chdir = /var/www/example.net/main/siteroot php_flag[display_errors] = Off php_flag[magic_quotes_gpc] = Off php_flag[track_vars] = On php_flag[register_globals] = Off php_value[include_path] = . php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/mod_php/session ;php_value[session.save_path] = /var/lib/php/fpm/session php_admin_value[max_input_vars] = 1800 php_admin_value[upload_max_filesize] = 18M php_admin_value[post_max_size] = 72M php_admin_value[expose_php] = Off php_admin_value[error_log] = /var/log/php-fpm/example.net_php-fpm.log php_admin_flag[log_errors] = On php_admin_flag[allow_url_fopen] = Off php_admin_value[upload_tmp_dir] = /tmp |
»»» example.net Unix socket configuration «««
touch /var/run/php-fpm/example_net.sock
chown root:root /var/run/php-fpm/example_net.sock
chmod 600 /var/run/php-fpm/example_net.sock
or in one go with Python:
python -c "import socket as s; sock = s.socket(s.AF_UNIX); sock.bind('/run/php-fpm/example_net.sock')"
example.org PHP-FPM configuration | /etc/php-fpm.d/example.org.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | ################# REMOVE THESE LINES #################### ### Pick *one* of these methods, TCP is commented out ### ################# REMOVE THESE LINES #################### ; Using TCP Sockets ;listen = 127.0.0.1:9003 ;listen.allowed_clients = 127.0.0.1 ; Using Unix Sockets listen = /var/run/php-fpm/example_org.sock listen.mode = 0600 user = weborg group = weborg pm = ondemand ;pm = dynamic pm.max_children = 4 pm.status_path = /statusfpm slowlog = /var/log/php-fpm/example.org_slow.log catch_workers_output = yes chdir = /var/www/example.org/main/siteroot php_flag[display_errors] = Off php_flag[magic_quotes_gpc] = Off php_flag[track_vars] = On php_flag[register_globals] = Off php_value[include_path] = . php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/mod_php/session ;php_value[session.save_path] = /var/lib/php/fpm/session php_admin_value[max_input_vars] = 1800 php_admin_value[upload_max_filesize] = 18M php_admin_value[post_max_size] = 72M php_admin_value[expose_php] = Off php_admin_value[error_log] = /var/log/php-fpm/example.org_php-fpm.log php_admin_flag[log_errors] = On php_admin_flag[allow_url_fopen] = Off php_admin_value[upload_tmp_dir] = /tmp |
»»» example.org Unix socket configuration «««
touch /var/run/php-fpm/example_org.sock
chown root:root /var/run/php-fpm/example_org.sock
chmod 600 /var/run/php-fpm/example_org.sock
or in one go with Python:
python -c "import socket as s; sock = s.socket(s.AF_UNIX); sock.bind('/run/php-fpm/example_org.sock')"
Add these 2 lines within the VirtualHost block of each of your sites’ configurations:
IMPORTANT NOTES
TCP Socket Method: $1 at the end expands to the entire request-URI from the original request, minus the leading slash.
Unix Socket Method: With this approach, the captured request URI ($1) is not passed after the path.
1 2 3 4 5 6 7 8 9 | ################################### ### Pick *one* of these methods ### ################################### ## PHP-FPM, Using TCP sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/example.com/main/siteroot/$1 ## PHP-FPM, Using Unix sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ unix://var/run/php-fpm/example-com.sock|fcgi://127.0.0.1:9000/var/www/example.com/main/siteroot/ |
1 2 3 4 5 6 7 8 9 | ################################### ### Pick *one* of these methods ### ################################### ## PHP-FPM, Using TCP sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9001/var/www/example.net/main/siteroot/$1 ## PHP-FPM, Using Unix sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ unix://var/run/php-fpm/example-net.sock|fcgi://127.0.0.1:9001/var/www/example.net/main/siteroot/ |
1 2 3 4 5 6 7 8 9 | ################################### ### Pick *one* of these methods ### ################################### ## PHP-FPM, Using TCP sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9002/var/www/example.org/main/siteroot/$1 ## PHP-FPM, Using Unix sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ unix://var/run/php-fpm/example-org.sock|fcgi://127.0.0.1:9002/var/www/example.org/main/siteroot/ |
Site 1
example.com Apache httpd configuration
/etc/httpd/conf.d/example.com.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <VirtualHost *:443> ## Main Server Configuration ## ServerName example.com ServerAlias www.example.com DocumentRoot /var/www/example.com/main/siteroot TransferLog /var/log/httpd/example.com_xfer_log CustomLog /var/log/httpd/example.com_access_log combined ErrorLog /var/log/httpd/example.com_error_log ################################### ### Pick *one* of these methods ### ################################### ## PHP-FPM, Using TCP sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/example.com/main/siteroot ## PHP-FPM, Using Unix sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ unix://var/run/php-fpm/example.com.sock|fcgi://127.0.0.1:9000/var/www/example.com/main/siteroot </VirtualHost> |
Site 2
example.net Apache httpd configuration
/etc/httpd/conf.d/example.net.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <VirtualHost *:443> ## Main Server Configuration ## ServerName example.net ServerAlias www.example.net DocumentRoot /var/www/example.net/main/siteroot TransferLog /var/log/httpd/example.net_xfer_log CustomLog /var/log/httpd/example.net_access_log combined ErrorLog /var/log/httpd/example.net_error_log ################################### ### Pick *one* of these methods ### ################################### ## PHP-FPM, Using TCP sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9001/var/www/example.net/main/siteroot ## PHP-FPM, Using Unix sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ unix://var/run/php-fpm/example.net.sock|fcgi://127.0.0.1:9000/var/www/example.net/main/siteroot </VirtualHost> |
Site 3
example.org Apache httpd configuration
/etc/httpd/conf.d/example.org.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <VirtualHost *:443> ## Main Server Configuration ## ServerName example.org ServerAlias www.example.org DocumentRoot /var/www/example.org/main/siteroot TransferLog /var/log/httpd/example.org_xfer_log CustomLog /var/log/httpd/example.org_access_log combined ErrorLog /var/log/httpd/example.org_error_log ################################### ### Pick *one* of these methods ### ################################### ## PHP-FPM, Using TCP sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9002/var/www/example.org/main/siteroot ## PHP-FPM, Using Unix sockets ## ProxyPassMatch ^/(.*\.php(/.*)?)$ unix://var/run/php-fpm/example.org.sock|fcgi://127.0.0.1:9000/var/www/example.org/main/siteroot </VirtualHost> |
Set the owner/group of directories and files starting at the DocumentRoot.
Set directory permissions to 755, set file permissions to 644.
SELinux Considerations
Problem:
PHP-FPM cannot start because it cannot bind to the defined ports:
C6: service php-fpm restart
C7: ssystemctl restart php-fpm.service
1 2 3 | Stopping php-fpm: [FAILED] Starting php-fpm: [19-Jan-2016 10:29:23] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Permission denied (13) [19-Jan-2016 10:29:23] ERROR: FPM initialization failed [FAILED] |
Solution:
Allow httpd connections to certain non-standard ports:
semanage port -a -t http_port_t -p tcp 9000
semanage port -a -t http_port_t -p tcp 9001
semanage port -a -t http_port_t -p tcp 9002
Other SELinux Considerations or Connection Problems
503 errors or connection refused errors in the error log:
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/example.com/main/siteroot'
restorecon -v '/var/www/example.com/main/siteroot'
or, to allow globally, instead of per site/directory:
setsebool -P httpd_unified 1
Other problems:
setsebool -P httpd_can_network_connect on
chcon -R -h -t httpd_sys_content_t /var/www/example.com/main/siteroot
chcon -R -h -t httpd_sys_script_rw_t /var/www/example.com/main/siteroot
Interesting SELinux Booleans
getsebool -a | grep user
getsebool -a | grep http
C6: service php-fpm restart
C7: ssystemctl restart php-fpm.service
1 2 | Stopping php-fpm: [FAILED] Starting php-fpm: [ OK ] |
Appendix
Replace the default Centos system PHP with IUS Repo PHP 5.6
1 2 3 4 5 6 7 | 1. rpm -qa --qf '%{name}\n' | grep -i php | sort -d > php.txt 2. rpm -qa | grep -i php | xargs rpm -e --nodeps 3. copy the package list in the next tab into a file called 'php56u.txt' 4. yum install `cat php56u.txt` 5. restart httpd ---C6: service httpd restart ---C7: systemctl restart httpd.service |
php56u-bcmath
php56u-cli
php56u-common
php56u-devel
php56u-fpm
php56u-gd
php56u-imap
php56u-intl
php56u-ldap
php56u-mbstring
php56u-mcrypt
php56u-mysqlnd
php56u-pdo
php56u-pear
php56u-pecl-apcu
php56u-pecl-geoip
php56u-pecl-imagick
php56u-pecl-jsonc
php56u-pecl-jsonc-devel
php56u-process
php56u-tidy
php56u-xml
php56u-xmlrpc
Replace IUS Repo PHP 5.6 with IUS repo PHP 7.0
1 2 3 4 5 6 7 | 1. rpm -qa --qf '%{name}\n' | grep -i php56u | sort -d > php56u.txt 2. sed -i 's/56u/70u/g' php56u.txt; mv php56u.txt php70u.txt 3. rpm -qa | grep -i php56u | xargs rpm -e --nodeps 4. yum install `cat php70u.txt` 5. restart httpd ---C6: service httpd restart ---C7: systemctl restart httpd.service |
php70u-cli
php70u-common
php70u-devel
php70u-embedded
php70u-fpm-httpd
php70u-fpm-nginx
php70u-fpm
php70u-gd
php70u-imap
php70u-intl
php70u-json
php70u-mbstring
php70u-mcrypt
php70u-mysqlnd
php70u-opcache
php70u-pecl-apcu
php70u-pecl-apcu-panel.noarch
php70u-pecl-xdebug
php70u-pdo
php70u-pear
php70u-process
php70u-pspell
php70u-recode
php70u-snmp
php70u-soap
php70u-tidy
php70u-xmlrpc
php70u-xml