Girders Blog
Notes on building internet applications

Setting Up PHP-FPM

Oct 18, 2021

To scale your PHP application, run it with PHP-FPM (FastCGI Process Manager). It is now the preferred way to run a PHP application. This post aims to clarify the concepts and settings of PHP-FPM in general. Actual installation commands are only a google search away.

Why FastCGI? What is FastCGI?

Originally, web servers provided the Common Gateway Interface (CGI) as a method to run programs to create dymanic content for a web request. Each CGI request caused the web server to fork a new process, initialize the runtime (ususally Perl), load the source code and run the program to produce content. This was great for simple scripts, but was too much overhead for more complicated applications.

The Apache web server introduced the mod_php module which ran PHP inside apache. With mod_php, the overhead of the per-process request was solved, but it could only scale vertically (larger servers), and likely had other runtime and security implications.

FastCGI pushed the application into a separate process outside the web server, even to one or more servers, allowing the application to scale horizontally (more servers). It uses a control process and worker or child processes to perform the reqeuests concurrently.

What is PHP-FPM?

FPM manages a pool of PHP processes which incoming web requests without the startup penalty. There is the manager process which controls the PHP processes, and multiple PHP processes to run the request. Since PHP is single-threaded for web requests, each process can only run a single request at a time. After it finishes, it is available for the next request.

The major difference between PHP-FPM and a Rails or Java application is that PHP-FPM offers “PHP as as service” whereas a Rails application runs the “Application as a service”. Therefore, PHP-FPM could run multiple applications at a time, while a Rails process runs a single application.

PHP-FPM can run multiple pools. Each pool is used to separate traffic between applications, hosting accounts, or request type (User-facing, API, Backend Requests, etc). This allows you to allocate resources appropriately and prevents one source of traffic from impacting another.

PHP-FPM does not accept HTTP requests. The FastCGI Protocol is a binary protocol that requires an adapter from a web server to send the request to the pool. Unfortunately, utilities like curl will not work. There are some FastCGI clients available on Github, but there is no standard command line utility that comes with FPM.

Installation

First, install additional PHP and any additional modules as needed and configure it in the php.ini file. Some things to consider in your configuration:

Next, install PHP-FPM if necessary using your package manager. Sometimes, these are bundled together.

Install PHP Opcache. This component is installed separately from PHP, and it is specific to the installed version of PHP you are running. Look for packages named something like “php-opcache” or “php80-opcache”.

Once installed, each pool needs to be configured (see below) and the PHP-FPM service started. Once running, any web server like NGINX can send requests to the service.

Pool Configuration

The default pool is “www” and if you are using only a simple configuration, go with this one. Otherwise, you may want a different pools for each application, account, or request type. The location of your pool configuration is system-dependent, but usually something like:

/etc/php-fpm.d/www.conf   [www pool configuration]

Each pool needs to “listen” to a different Unix file socket or port. The web server will proxy incoming http requests to the port or socket on the application server. Choose where to host your pool in either one of these formats:

listen = 127.0.0.1:9000
listen = /path/to/unix/socket

You also may need a custom Unix user to run the pool as.

user = myapp
group = myapp

Worker Configuration

Now we need to configure how many children or worker processes we will need. Here are the relevant settings:

You need enough workers to process in number of concurrent incoming requests. Configure how many workers to start, and how many idle workers to be ready and the maximum workers your system can handle (load, database resources, etc.) Setting the lifetime of a worker is a balance between any memory bloat or extra database connections to reclaim. Your configuration may look something like this:

pm.max_children = 15
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 1000

When you are ready, start the PHP-FPM service on your server, and ensure it will come up after a restart of the server.

Opcache

A Rails or Java “Application Service” load the code or byte-code on startup. Since PHP-FPM functions differently, use the PHP opcache to cache files and compiled byte-code in shared memory across all the workers. This improves performance and lowers the memory footprint.

In your php.ini file, configure the Opcache settings. They are commented out by default and are well documented with inline comments. Sometimes though, they are found in the etc/php/10-opcache.ini or similar file.

Add or Uncomment and modify the following settings, modified for your needs:

opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.interned_strings_buffer=8
opcache.revalidate_freq=200

Restart PHP-FPM after your change your configutions for PHP or PHP-FPM.

Install your application

Since PHP-FPM is a “PHP runtime as a service” process, it does not know about your application. It needs the PHP source code of your application to be installed on the same server as FPM. To configure NGINX, we need to know the application path to tell FastCGI what application to run.

PHP-FPM with NGINX

NGINX provides a FastCGI client out of the box to easily proxy web requests to the FPM process. NGINX is mostly a file server (serving HTML or other files) and proxy server, sending requests to other processes. It does not usually run processes itself as Apache does.

The /etc/nginx/ directory ships with a fastcgi_params file that you can use for default FastCGI settings. Include that in your location block. Because each use case is unique, your configuration will vary.

location / {
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_send_timeout      360;
    proxy_read_timeout      360;
    fastcgi_param           DOCUMENT_ROOT   /apps/myapp/index.php;
    fastcgi_param           SCRIPT_FILENAME /apps/myapp/index.php;
    fastcgi_index           index.php;
    fastcgi_pass            127.0.0.1:9000;
    include                 fastcgi_params;
}

Before this, you could configure a “location /api/” block that forwards to the FPM API pool listening on 127.0.0.1:9001.

HAProxy with PHP-FPM

This post on the HAProxy blog shows you how to setup HTProxy to forwaard requests to PHP-FPM.

Database Connection Pooling

PHP-FPM does not use a database connection pool since it runs across processes and even applications. Instead, it relies on PHP’s persistent connection functions like pg_pconnect, mysql_pconnect, and the Redis module’s pconnect to cache the connected resource between requests on each process. These connection functions are likely what your framework of choice already uses.

You need to ensure your database has enough connections available for your “max_children” settings and the number of connections your application requires per request.

Also, in php.ini, set the maximum persistent connections. This will be per process, so consider if you will have many different databases being accessed and retained in the persistence cache.

pgsql.max_persistent = 15

Summary

After install, you need to determine how many resources you need to allocate on your server or determine your Virtual Server size. Considerations in configuring your application service discussed earlier include: