How to Use PHP-FPM to Secure Your VPS Environment

Introduction

Here at Sucuri we deal with plenty of clients who have what could best be described as “soup kitchen servers”: Web hosting servers with a huge number of websites all crammed into the same environment.

These environments often have a laundry list of neglected websites with vulnerable software components and frequently have poorly configured file permissions and ownership.

The main issue with hosting a large number of websites on one shared server environment is that if (and when) one website becomes infected with malware, it can easily spread to all of the other websites in the same container. In other words: A single compromised administrator password can take down your entire fleet of websites and cause a major headache for both you and your clients.

Fortunately, there are ways to configure your server where this “cross site contamination” is not possible. If you are using commercial, proprietary software the most common solution is to use WHM to create a separate cPanel instance for each website, however this requires ongoing costs and license fees. If you administer a large number of websites, these costs can add up quickly. On the other hand, with the right know-how you can configure and maintain your own “homebrew” environment with PHP-FPM to host multiple websites on a single, secure VPS environment.

Note:

This configuration will not prevent your websites from being hacked, but it will help reduce the risk of malware spreading between websites. We also always recommend regularly updating and patching your VPS to ensure that all security updates are configured properly.

1. Getting started

Before we begin you’ll need to ensure that your VPS is installed and ready to go! You’ll need to ensure that you have the following already configured:

LEMP stack

This includes the following

  • Linux
  • NGINX
  • MySQL (or MariaDB)
  • PHP

Updates and security patches

Make sure that you’ve installed all available updates and security patches.  We are using NGINX here rather than Apache because this configuration is designed for hosting multiple websites on a single server and this will perform and scale much better.

Non-root user

We will also need a non-root user added to the sudoers file.

Key-based authentication

We recommend using key-based authentication rather than a password for SSH/SFTP access.

Security policies

There are also a few basic security policies we’d recommend enabling before we get started as well:

  • Enable the UFW firewall
  • Configure SSH to use a non-standard port
  • Restrict SSH access to only known-good IP addresses

The UFW firewall is actually very easy to configure and use, although it’s a commonly skipped step during the initial server propagation and is often seen as a nuisance. For brevity’s sake we won’t go over all of these in this guide.

2. Delete the default site

First off, connect to your server via SSH using a non-root sudo user.

Once you’re connected, let’s begin by tidying up the environment a little bit. NGINX comes preinstalled with a default/placeholder website configuration.

Let’s go ahead and move that out of the way, as it is not necessary to have here:

				
					$ sudo cp /etc/nginx/sites-enabled/default 
/etc/nginx/sites-enabled/default-disabled
				
			

3. Create new users

Each website is going to need its own linux user; this will allow us to both assign the website files to their respective users as well as ensure that the background PHP processes running are separated. This is what helps to ensure that cross-site-contamination is not possible.

For the purposes of this tutorial we’re going to create just two users but be sure to create one user for each different website that you want hosted in this environment.

We can create these by running the following commands:

				
					$ sudo useradd -m user1
$ sudo useradd -m user2
				
			

The -m flag will also create a properly configured home directory for the user within the /home directory which we will need later on.

Next, configure the users so that they can interact with the web server:

				
					$ sudo usermod -a -G user1 www-data
$ sudo usermod -a -G user2 www-data
				
			

Now if you run the following command:

				
					$ ls -la /home
				
			

You should see the newly created home directories for your new users. The permissions should also already be correctly set as 750:

				
					drwxr-xr-x  5 root     root       4096 Feb 24 23:26 .
drwxr-xr-x 19 root     root       4096 Feb 24 22:52 ..
drwxr-x---  4 sudouser sudouser   4096 Feb 24 23:27 sudouser
drwxr-x---  2 user1    user1      4096 Feb 24 23:26 user1
drwxr-x---  2 user2    user2      4096 Feb 24 23:25 user2
				
			

Now let’s go ahead and add a public_html directory inside each of those users’ homes so that the websites have a directory above their webroot to work with if they see fit.

First you’ll need to escalate your privileges in order to have write access:

				
					$ sudo su
				
			

Then create the directories and adjust their ownership and permissions properly:

				
					# cd /home/user1
# mkdir public_html
# chown user1:user1 public_html
# chmod 750 public_html

# cd /home/user2
# mkdir public_html
# chown user2:user2 public_html
# chmod 750 public_html
				
			

4. Generate PHP-FPM pools

Now we need to create the PHP-FPM pools which will allow the backend processes to run separately from the web server itself. This is one of the key components which will help prevent cross-site contamination.

Please note that depending on the version of PHP that you’ve installed on the server this might look a little bit different. At the time of writing this guide WordPress is still in beta compatibility mode for PHP 8.1, although all remaining PHP 8.1 issues are merely deprecation notices so it should still function just fine. 

If you’re migrating over older WordPress websites you’ll need to double check this PHP compatibility chart to ensure functionality.

There should be a default pool located in the following file:

				
					/etc/php/8.1/fpm/pool.d/www.conf
				
			

Let’s go ahead and copy the contents of that file into two new pools that we will use for our websites:

				
					$ sudo cp /etc/php/8.1/fpm/pool.d/www.conf /etc/php/8.1/fpm/pool.d/fpm-user1.conf

$ sudo cp /etc/php/8.1/fpm/pool.d/www.conf /etc/php/8.1/fpm/pool.d/fpm-user2.conf
				
			

Next, let’s tidy up a bit by removing that default pool as it is no longer needed:

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

5. Assign pools to users

Next, we will assign these newly created pools to the users that we generated earlier on. We can do this by editing the FPM configuration files that we just created!

Let’s open the first configuration file with the following command:

				
					$ sudo vim /etc/php/8.1/fpm/pool.d/fpm-user1.conf
				
			

You don’t have to use vim of course; other command-line text editors such as nano are available as well, but vim comes preinstalled on many servers so it’s already at your fingertips! 

If you’re more comfortable using a more traditional text editor then you can connect to your server using FIlezilla or some other FTP client, navigate to the correct directory and open them up directly.

We will need to change four configuration values here.

Pool name

  • change [www] to [user1]

User

  • change user = www-data to user = user1

Group

  • change group = www-data to group = user1

Listen

The listen line by default should look like this:

				
					listen = /run/php/php8.1-fpm.sock
				
			

We are going to change it to this:

				
					listen = /run/php/php8.1-fpm-user1.sock
				
			

Now, repeat this step for user2!

				
					$ sudo vim /etc/php/8.1/fpm/pool.d/fpm-user2.conf
				
			

6. Restart PHP-FPM

To ensure that all of our changes take effect, go ahead and restart the PHP-FPM process.

				
					$ sudo service php8.1-fpm restart
				
			

Double check that the configuration has taken hold correctly like so:

				
					$ sudo service php8.1-fpm status

				
			

You should now see your separate PHP-FPM processes enabled and running alongside their associated process IDs:

				
					CGroup: /system.slice/php8.1-fpm.service
            ├─27482 "php-fpm: master process (/etc/php/8.1/fpm/php-fpm.conf)
            ├─27484 "php-fpm: user1 user1
            ├─27485 "php-fpm: user1 user1
            ├─27486 "php-fpm: user2 user2
            └─27487 "php-fpm: user2 user2
				
			

Want to protect your websites?

Our Website Application Firewall (WAF) blocks bad bots, virtually patches known vulnerabilities, and intercepts XSS, RCE, and SQLi attacks.

7. Configure NGINX

Now we’ll need to configure NGINX to house our websites properly. This will involve creating the websites and adjusting their configurations. Let’s begin with website number one:

				
					$ sudo vim /etc/nginx/sites-available/user1
				
			

Then, copy and paste the following into the file (if using vim, don’t forget to first enter insert mode by pressing i)

Pro tip: To paste in an SSH terminal hold down CTRL + SHIFT followed by pressing V

				
					server {
	server_name website1.com;
	access_log /var/log/nginx/user1.access.log;
	error_log /var/log/nginx/user1.error.log;
	root /home/user1/public_html;
	index index.php;
	try_files $uri $uri/ /index.php?$query_string;
	location ~ \.php$ {
        	fastcgi_pass unix:/var/run/php/php8.1-fpm-user1.sock;
        	include snippets/fastcgi-php.conf;
	}
}

				
			

Be sure to change the following values for your specific website:

  • The domain name (after server_name)
  • Access and error log names
  • The respective home directory (should be /home/user1/public_html)
  • The PHP-FPM sock name

You may notice that the path pointing to the FPM sock file in the above configuration contains /var/ whereas the previous references did not. Depending on the operating system and server configuration of your VPS you may need to tinker with that if you get errors.

Now, save and exit.

At this time you can do the same for user2. Don’t forget to change the values for the second website!

Let’s now associate the files with their corresponding NGINX directories:

				
					$ sudo ln -s /etc/nginx/sites-available/user1 /etc/nginx/sites-enabled/user1

$ sudo ln -s /etc/nginx/sites-available/user2 /etc/nginx/sites-enabled/user2
				
			

Now, restart the web server.

				
					$ sudo service nginx restart
				
			

8. Verify the installation

To ensure that all systems are ready to go, we can test out the websites with a simple phpinfo script.

Go ahead and create a PHP script called info.php in one of the users’ public_html home directories. 

First, let’s su into the user in question:

				
					$ sudo su
# cd /home/user1/public_html
# su user1
				
			

Next, create the info.php file with the following content:

				
					<?php
phpinfo();
				
			

Access the file in your browser. If you see the PHPInfo screen then you were successful!

Be sure to remove this script when you are done! It gives out a large amount of information about your web server that should not be public.

Next, let’s confirm that cross-site contamination is not possible. Since you’re still logged in as user1, try creating a file in the user2 home directory:

				
					$ touch /home/user2/public_html/test.txt
touch: cannot touch '/home/user2/public_html/test.txt': Permission denied
				
			

Voila! Not only is the file ownership configured for the respective users, but the underlying PHP-FPM process running on the server itself runs as their respective users.

The above command of course is run directly on the server, but the way that we have configured this should prevent write-access and cross-contamination between websites on the application/website level as well.

Congratulations, you now have a working and secure VPS hosting configuration! Now you can put your feet up and have a cold one to celebrate.

9. HTTPS/SSL

If your websites are behind our Sucuri Firewall service then the server configuration should be ready to go, and this should all be compatible with HTTPS/SSL, as the firewall service should take care of the HTTPS portion automatically. 

If you are not using our firewall or a similar service and you would prefer to handle the SSL encryption yourself then you will need to add some additional rules to your NGINX configuration file. This guide on the official NGINX website reviews the additional configurations necessary for SSL and how to get it automatically set up for you, but it will likely look something like this (within the primary server configuration block):

				
					listen 80;
listen 443 ssl;

server_name website1.com www.website1.com;

ssl_certificate /etc/nginx/ssl/your_certificate.crt;
ssl_certificate_key /etc/nginx/ssl/your_private_key.key;
				
			

Please note that handling SSL traffic at your server means your server will now be responsible for the SSL handshake and encryption/decryption, which can be computationally intensive. So, make sure that your server has the necessary resources to handle this additional load.

10. Disable direct IP access

One additional security consideration that we’ll want to implement is to prevent the websites from being accessed in a browser using the hosting IP address.

If you put the host IP within your browser address bar and navigate to it you should see one of your websites display. This should be disabled for security reasons.

We can do this by editing one of the website’s NGINX configuration files. Let’s open op the sites-available config file of the website which is showing in your browser when you access the IP directly:

				
					/etc/nginx/sites-available/user1
				
			

We’re going to add the following to the top of the configuration:

				
					server {
	listen  	80 default_server;
	listen  	[::]:80 default_server;
	server_name _;
	return  	444;
}

server {
	listen  	80;
	listen  	[::]:80;

	server_name website1.com;

continued...

				
			

What this will do is return a 444 response when attempting to access the IP directly, which will close the connection and not give any response from the server.

11. Enable firewall bypass prevention

If you are a user of our website firewall service now would be an optimal time to enable firewall bypass prevention on your websites. This will ensure that all web traffic destined to your websites gets filtered through our firewall service and greatly improves security.

Once we’ve confirmed that the initial setup is configured and working properly we will improve our security with bypass prevention.

We’ll go ahead and edit the NGINX configuration file like so:

				
					$ sudo vim /etc/nginx/sites-available/user1
				
			

Add the following rules located in your firewall dashboard here to the bottom of the file before the closing } bracket:

				
					location / {
	allow 192.88.134.0/23;
	allow 185.93.228.0/22;
	allow 2a02:fe80::/29;
	allow 66.248.200.0/22;
	allow 208.109.0.0/22;
	deny all;
	# Existing NGINX rules
}

				
			

Save and exit the file. You may need to restart the NGINX server again for the changes to take effect.

You can go ahead and test the configuration by running the following command from your local machine, not the server:

				
					$ curl -H "host: website1.com" http://host_ip_address -kIL
				
			

If the bypass prevention rule was successful you should receive the following response:

				
					HTTP/1.1 403 Forbidden
				
			

This is because we have instructed the NGINX web server to only accept web traffic through our firewall IP list.

Conclusion

Although this guide may be a little bit more technically challenging if you’re accustomed to only using graphic user interfaces, it can really help to have some command line knowledge and get things configured yourself so you understand the background environment on which your websites are hosted.

Remember, if you receive any errors or your websites do not load properly after the initial setup don’t forget to check the error log files that were specified earlier on in your NGINX configuration.

If you are migrating over live websites to a new VPS server using this configuration, you may first want to get them set up by editing your local hosts file to finish the setup and troubleshooting before switching the live DNS over in order to reduce downtime.

Also, don’t forget to regularly check for updates and issue any security patches to your VPS in order to keep your environment safe and sound!

Security you can depend on.

Protect your websites and server against credit card skimmers, backdoors, SEO spam, and other malware.

PHP-FPM FAQs

  • What is PHP-FPM?

    PHP-FPM is an advanced FastCGI process manager for PHP, providing a significant performance boost over traditional CGI-based methods. It provides a robust and scalable architecture for PHP applications, allowing a secure separation of different websites running on the same server. This isolation is particularly useful when you're trying to avoid cross-contamination of malware, a common issue when hosting multiple sites on the same VPS.

  • What is cross-site contamination?

    When multiple websites are all crammed into the same cPanel instance or all just thrown into the default directory of their hosting server (owned by the same user, no less) this can create a perfect environment for malware to spread between websites.

    For example, a single compromised administrator user on a WordPress site can bring down your entire fleet of websites and cause a major headache (and cost a lot of money to get repaired).

  • Does WordPress work with PHP-FPM?

    WordPress is a CMS, or Content Management System. Like many other CMS, it's developed entirely in PHP, a popular scripting language. PHP-FPM plays a crucial role in how WordPress operates. It works by executing a script in real-time one time, and then storing that script in cache. This means the next time the same script needs to be loaded, instead of re-executing and using more resources, it simply retrieves it from the cache memory. This greatly optimizes the performance of WordPress.

  • Does Magento work with PHP-FPM?

    Magento is a CMS, or Content Management System, much like WordPress, but with a focus on e-commerce. It's built entirely in PHP, and PHP-FPM plays a vital role in how Magento functions. PHP-FPM optimizes the use of memory and CPU resources for Magento sites, enabling them to operate more efficiently. This not only improves content management capabilities but also accelerates the speed at which websites are served. This means faster loading times and a smoother overall user experience.

Sucuri Resource Library

Say on top emerging website security threats with our helpful guides, email, courses, and blog content.

Webinar

Learn how to identify issues if you suspect your WordPress site has been hacked.

Email Course

Join our email series as we offer actionable steps and basic security techniques for WordPress site owners.

Report

Based on our data, the three most commonly infected CMS platforms were WordPress, Joomla! and Magento.