Use PHP-FPM Pools to Secure Multiple Web Sites

Updated on November 21, 2023
Use PHP-FPM Pools to Secure Multiple Web Sites header image

Introduction

As server hardware gets faster and more efficient, it's no longer uncommon to host multiple websites on Nginx web server. For better performance, process isolation, and security, consider using separate PHP FastCGI Process Manager (PHP-FPM) pools for each website. This article describes the basics of using separate PHP-FPM pools for multiple websites on Nginx with PHP 7.4, using a Vultr Ubuntu 20.04 LTS cloud server instance. This allows you to host multiple virtual domains using Nginx server blocks.

Prerequisites

1. Remove the Default Site

Nginx installs a default site which is redundant for this article. To remove the default site, SSH to the server as a non-root sudo user and run:

$ sudo rm /etc/nginx/sites-enabled/default 

2. Create the Site Users

Each site needs to run as a different user for security purposes and isolation. To do that, create two user accounts and assign the www-data user to their respective groups. This allows the web server to interact with the users and vice-versa. Do not give the site1 and site2 users login privileges or associate any other information to the accounts. To create the users, run:

$ sudo useradd site1
$ sudo useradd site2
$ usermod -a -G site1 www-data
$ usermod -a -G site2 www-data

3. Assign Directory Permissions

Create two directories and lock down the permissions to prepare the server for the two separate sites.

$ sudo mkdir /var/www/site1
$ sudo chown -R site1:site1 /var/www/site1
$ sudo mkdir /var/www/site2
$ sudo chown -R site2:site2 /var/www/site2
$ sudo chmod 770 /var/www/site2

The Unix permissions are 770. Each user and the user's associated group has full permissions (7) on the directory, but the world has no (0) permission. This setting restricts the site1 user from seeing data for site2 and vice-versa.

4. Create New PHP-FPM Pools

Copy the default PHP-FPM pool as a template for the two new user pools:

$ sudo cp /etc/php/7.4/fpm/pool.d/www.conf /etc/php/7.4/fpm/pool.d/fpm-site1.conf
$ sudo cp /etc/php/7.4/fpm/pool.d/www.conf /etc/php/7.4/fpm/pool.d/fpm-site2.conf

Delete the unneeded default pool.

$ sudo rm /etc/php/7.4/fpm/pool.d/www.conf

5. Configure the First Pool

Each pool has an associated user and Unix socket. Edit the first configuration file:

$ sudo nano /etc/php/7.4/fpm/pool.d/fpm-site1.conf

Change four lines:

  • Change the top line inside the brackets that sets the pool name from [www] to [site1].
  • Change the line user = www-data to user = site1.
  • Change the line group = www-data to group = site1.
  • Change the line listen = /var/run/php/php7.4-fpm.sock to listen = /var/run/php/php7.4-site1-fpm.sock.

Save the file and exit.

6. Configure the Second Pool

Edit the second configuration file:

$ sudo nano /etc/php/7.4/fpm/pool.d/fpm-site2.conf

Change four lines:

  • Change the top line inside the brackets that sets the pool name from [www] to [site2].
  • Change the line user = www-data to user = site2.
  • Change the line group = www-data to group = site2.
  • Change the line listen = /var/run/php/php7.4-fpm.sock to listen = /var/run/php/php7.4-site2-fpm.sock.

Save the file and exit.

7. Restart PHP-FPM

Restart the PHP-FPM daemon.

$ sudo service php7.4-fpm restart

The daemon should restart without errors. Verify two separate PHP-FPM pools are running.

$ sudo service php7.4-fpm status

If the FPM service pools are correct they are visibly forked in the process list:

 CGroup: /system.slice/php7.4-fpm.service
         ├─70796 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
         ├─70807 php-fpm: pool site1
         ├─70808 php-fpm: pool site1
         ├─70809 php-fpm: pool site2
         └─70810 php-fpm: pool site2

The actual process IDs may differ from the ones listed above.

8. Create the Nginx Sites

The server needs two new sites to use the two PHP-FPM pools, one associated with each respective pool.

  1. Create a configuration file for site1:

     $ sudo nano /etc/nginx/sites-available/site1
  2. Paste the following into the file:

     server {
         server_name site1.example.com;
    
         access_log /var/log/nginx/site1.access.log;
         error_log /var/log/nginx/site1.error.log;
    
         root /var/www/site1;
    
         index index.php;
    
         try_files $uri $uri/ /index.php?$query_string;
    
         location ~ \.php$ {
                 fastcgi_pass unix:/var/run/php/php7.4-fpm-site1.sock;
                 include snippets/fastcgi-php.conf;
         }
     }
  3. Save and exit the file. The important changes above are:

    • server_name - the fully qualified DNS name of the host
    • access_log - the location and name of the access log
    • error_log - the location and name of the error log
    • root - the location on the files on disk that the web server uses
    • fastcgi_pass - the location of the PHP-FPM sock created by the pool
  4. Make a similar file for site2.

     $ sudo nano /etc/nginx/sites-available/site2
  5. The contents of this file matches site1, except it has the information for site2:

     server {
         server_name site2.example.com;
    
         access_log /var/log/nginx/site2.access.log;
         error_log /var/log/nginx/site2.error.log;
    
         root /var/www/site2;
    
         index index.php;
    
         try_files $uri $uri/ /index.php?$query_string;
    
         location ~ \.php$ {
                 fastcgi_pass unix:/var/run/php/php7.4-fpm-site2.sock;
                 include snippets/fastcgi-php.conf;
         }
     }
  6. Save and exit the file.

  7. Link the files to the respective Nginx directories.

     $ sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1
     $ sudo ln -s /etc/nginx/sites-available/site2 /etc/nginx/sites-enabled/site2
  8. Restart Nginx.

     $ sudo service nginx restart

This should start without error.

9. Test the Configuration

Add a test file in the root of each site to test the configuration and verify which user serves each web site.

  1. Create an index file for site1.

     $ nano /var/www/site1/index.php
  2. Paste the following into the file:

     <?php
     phpinfo();
  3. Save and exit the file.

  4. Create an index file for site2.

     $ nano /var/www/site2/index.php
  5. Paste the following into the file for site2:

     <?php
     phpinfo();
  6. Save and exit the file.

  7. Open a browser and visit http://site1.example.com and http://site2.example.com.

Examine the PHP Information page for each site. In the PHP Variables section, the variable $_SERVER['USER'] should be site1 for site1.example.com, and site2 for site2.example.com.

Summary

Creating separate PHP-FPM pools for each website served on a single server gives the administrator stronger security, more defined boundaries, and makes it easier to troubleshoot problems associated with individual sites. It's also possible to individually tune site settings for performance. These settings are in the /etc/php/7.4/fpm/pool.d configuration files. The main setting that is often changed is the pm setting, which controls process creation, with settings like dynamic, static or ondemand. See the FastCGI Process Manager Configuration for more information.