July 17

Nginx and PHP-FPM, bash script for creating new vhost’s under separate fpm pools

Using worker pools in PHP-FPM can allow you to easily separate out and isolate virtual hosts that make use of PHP. PHP-FPM allows you to run multiple pools of processes all spawned from the master one and each pool can run as a different user and/or group. Each pool can be further isolated by running in a chroot environment and by overriding the default php.ini values on a per pool basis.

Running PHP for each vhost under a different user/group can help to stop a vulnerability in one site potentially exposing another vhost, it can also stop one malicious owner of a vhost from been able to use PHP to access the files of another site owned by someone else on the same server (in a shared hosting environment).

The process of setting up the web server config and a new PHP-FPM pool for each new vhost on a server can become a rather time consuming and boring process. However as this process follows a fairly standard set of steps it can be easily scripted.

The following bash script was designed for use on Debian and Ubuntu, however it only needs a few modifications to use it on CentOS (the lines should be commented in the script).

What does the script do:

  • Creates a new system user for the site
  • Creates a new vhost config file for nginx using a basic template
  • Creates a new PHP-FPM pool with the uid and gid set to that of the new system user
  • Creates a new directory for the site, within the new users home directory
  • Reloads Nginx to allow the new vhost to be detected
  • Restarts PHP-FPM to generate the new pool of PHP workers

How it works:

Nginx:

Under your Nginx config directory (probably /etc/nginx) you need to create two new directories (if you don’t already have them):

  • sites-available
  • sites-enabled

In the “sites-available” dir goes the the individual config files for each vhost, each new vhost using a new file. Under the “sites-enabled” directory goes a symlink to the config file in the sites available directory, this gives you the ability to disable sites without removing the contents of the site or its config. You will also need to add the following (within the http section) to main Nginx config file (nginx.conf – lives under /etc/nginx/nginx.conf on debian) to get Nginx to automatically look in the sites-enabled directory.

http {
  ...
 
  # Load all vhosts !
  include /etc/nginx/sites-enabled/*.conf;
}

Replacing the path of the your nginx configuration if needed.

The web root of the new site will be /home/<new user>/public_html if you’re setting up hosting for a PHP site that holds most of its code outside of the web root (most MVC frameworks do this such as Symfony and FuelPHP) then you can specify the web root directory interactively when the script runs.

PHP-FPM

By default the script will put the config file for new pools under /etc/php5/fpm/pool.d, however you can easily change this at the top of the script to where ever you want. You will need to add the following line to your php-fpm.conf file (lives under: /etc/php5/fpm/php-fpm.conf typically on Debian):

include=/etc/php5/fpm/pool.d/*.conf

This will tell PHP-FPM where to look for other config files, which in this case will be the individual pool config files (one per pool).

How to use it:

Simply download the tar file at the end of the this article and extract it. If your not sure how to extract a tar file either read the man pages(man tar) or use the command below:

tar -xzf create_php_vhost.tar.gz

Once you have extracted the archive you will need to change the permissions on the create_php_site.sh file to make it executable (if it isn’t already):

chmod u+x create_php_site.sh

Then run the create_nginx_site.sh script passing to it the domain name as the only parameter.

./create_php_site.sh example.com

The script will then take you through the steps required to setup the Nginx configuration, the new system user and the PHP-FPM pool. Once all that is complete it will restart Nginx and PHP-FPM ready for your new vhost.
An example screen session is shown below:

./create_php_site.sh example.com
Creating hosting for: example.com
Please specify the username for this site?
example
Adding user `example' ...
Adding new group `example' (1000) ...
Adding new user `example' (1000) with group `example' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for example
Enter the new value, or press ENTER for the default
        Full Name []: Example User
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] y
Would you like to change to web root directory (y/n)?
y
Enter the new web root dir (after the public_html/)
web
How many FPM servers would you like by default:
2
Min number of FPM servers would you like:
1
Max number of FPM servers would you like:
5
Reloading nginx configuration: nginx.
Restarting PHP5 FastCGI Process Manager: php5-fpm.
 
Site Created for example.com with PHP support

The Script:

#!/bin/bash
# @author: Seb Dangerfield
# http://www.sebdangerfield.me.uk/?p=513 
# Created:   11/08/2011
# Modified:   07/01/2012
# Modified:   27/11/2012
 
# Modify the following to match your system
NGINX_CONFIG='/etc/nginx/sites-available'
NGINX_SITES_ENABLED='/etc/nginx/sites-enabled'
PHP_INI_DIR='/etc/php5/fpm/pool.d'
WEB_SERVER_GROUP='www-data'
NGINX_INIT='/etc/init.d/nginx'
PHP_FPM_INIT='/etc/init.d/php5-fpm'
# --------------END 
SED=`which sed`
CURRENT_DIR=`dirname $0`
 
if [ -z $1 ]; then
	echo "No domain name given"
	exit 1
fi
DOMAIN=$1
 
# check the domain is valid!
PATTERN="^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])quot;;
if [[ "$DOMAIN" =~ $PATTERN ]]; then
	DOMAIN=`echo $DOMAIN | tr '[A-Z]' '[a-z]'`
	echo "Creating hosting for:" $DOMAIN
else
	echo "invalid domain name"
	exit 1 
fi
 
# Create a new user!
echo "Please specify the username for this site?"
read USERNAME
HOME_DIR=$USERNAME
adduser $USERNAME
# -------
# CentOS:
# If you're using CentOS you will need to uncomment the next 3 lines!
# -------
#echo "Please enter a password for the user: $USERNAME"
#read -s PASS
#echo $PASS | passwd --stdin $USERNAME
 
echo "Would you like to change to web root directory (y/n)?"
read CHANGEROOT
if [ $CHANGEROOT == "y" ]; then
	echo "Enter the new web root dir (after the public_html/)"
	read DIR
	PUBLIC_HTML_DIR='/public_html/'$DIR
else
	PUBLIC_HTML_DIR='/public_html'
fi
 
# Now we need to copy the virtual host template
CONFIG=$NGINX_CONFIG/$DOMAIN.conf
cp $CURRENT_DIR/nginx.vhost.conf.template $CONFIG
$SED -i "s/@@HOSTNAME@@/$DOMAIN/g" $CONFIG
$SED -i "s#@@PATH@@#\/home\/"$USERNAME$PUBLIC_HTML_DIR"#g" $CONFIG
$SED -i "s/@@LOG_PATH@@/\/home\/$USERNAME\/_logs/g" $CONFIG
$SED -i "s#@@SOCKET@@#/var/run/"$USERNAME"_fpm.sock#g" $CONFIG
 
echo "How many FPM servers would you like by default:"
read FPM_SERVERS
echo "Min number of FPM servers would you like:"
read MIN_SERVERS
echo "Max number of FPM servers would you like:"
read MAX_SERVERS
# Now we need to create a new php fpm pool config
FPMCONF="$PHP_INI_DIR/$DOMAIN.pool.conf"
 
cp $CURRENT_DIR/pool.conf.template $FPMCONF
 
$SED -i "s/@@USER@@/$USERNAME/g" $FPMCONF
$SED -i "s/@@HOME_DIR@@/\/home\/$USERNAME/g" $FPMCONF
$SED -i "s/@@START_SERVERS@@/$FPM_SERVERS/g" $FPMCONF
$SED -i "s/@@MIN_SERVERS@@/$MIN_SERVERS/g" $FPMCONF
$SED -i "s/@@MAX_SERVERS@@/$MAX_SERVERS/g" $FPMCONF
MAX_CHILDS=$((MAX_SERVERS+START_SERVERS))
$SED -i "s/@@MAX_CHILDS@@/$MAX_CHILDS/g" $FPMCONF
 
usermod -aG $USERNAME $WEB_SERVER_GROUP
chmod g+rx /home/$HOME_DIR
chmod 600 $CONFIG
 
ln -s $CONFIG $NGINX_SITES_ENABLED/$DOMAIN.conf
 
# set file perms and create required dirs!
mkdir -p /home/$HOME_DIR$PUBLIC_HTML_DIR
mkdir /home/$HOME_DIR/_logs
mkdir /home/$HOME_DIR/_sessions
chmod 750 /home/$HOME_DIR -R
chmod 700 /home/$HOME_DIR/_sessions
chmod 770 /home/$HOME_DIR/_logs
chmod 750 /home/$HOME_DIR$PUBLIC_HTML_DIR
chown $USERNAME:$USERNAME /home/$HOME_DIR/ -R
 
$NGINX_INIT reload
$PHP_FPM_INIT restart
 
echo -e "\nSite Created for $DOMAIN with PHP support"

Download:

Please note both downloads contain the same files, so you only need to download one.