Configuring nginx for PHP-FPM

PHP FastCGI Process Manager (or PHP-FPM for short) is an daemon that remains resident in memory and processes requests relayed to it either using a local domain socket (preferred) or a TCP socket (not preferred but sometimes the only option). In order to support this side loading/executing of PHP scripts it uses its own protocol which (as the name implies) is based on regular CGI behavior.

Contents

What is FastCGI?

This FastCGI option stands in opposition to the two other main means of deploying the PHP interpreter: CGI and as an embedded server module. These other two options have a great number of performance drawbacks. CGI requires an executable be called on each and every attempt to interpret a PHP script. Embedding a PHP interpreter in the web server does save this invocation time but also results in immense overhead now that each worker process carries with it another interpreter even if the request currently being served is just for CSS or a PNG.

Considering these drawbacks it’s considered better to merge the already-in-memory-and-running benefits of a server module with the only-for-related items behavior of CGI. For this reason the current standard seems to be PHP-FPM where its worker processes can scale up as needed but only for PHP-related work.

nginx only supports the use of FastCGI so if you’re using that web server your decision has already been made there.

Installing and Running PHP-FPM

On ubuntu-based system:

root@f4ef9153e9b1:/# apt-get install -y php-fpm

Which makes the php-fpm7.0 executable available as well as the upstart service of the same name.

On CentOS-based system:

[root@4cdafe4fd91d /]# yum install -y php-fpm

Which makes the php-fpm executable available as well as the systemd unit of the same name.

Deployment will consist of either executing the given executable directly for containers (on Ubuntu, you must give the -F option to keep it from daemonizing) or configuring the respective service to start on boot for VM’s and physical installs.

For Ubuntu, the default behavior is for PHP-FPM to listen over a Unix domain socket, which I would recommend that you leave it at unless you have a particular reason to tell it to use TCP. On CentOS for reasons only Red Hat understands, TCP (and not Unix domain) is the default. For CentOS you’ll want to open and edit the [www] section such that the listen directive is set to a Unix domain socket:

listen = /run/php/php7.0-fpm.sock;

and restart PHP-FPM if already running. In either case you’ll need to make sure the mode on the domain socket is readable by setting a good value to listen.mode for example listen.mode = 0777 will leave permissions wide open (alright for personal development but probably not ideal for production services.

PHP-FPM itself has some tunables and configuration options but I’ll leave that for a later article as this one is strictly about getting PHP-FPM and nginx working together.

Configuring nginx For PHP-FPM

Now we get to the part we’ve all been waiting for. You have your existing nginx server and want to configure it to delegate execution of PHP scripts over to PHP-FPM. With nginx there are no special modules to load, all the web server needs to understand is the FastCGI protocol which is loaded as part of the regular nginx startup.

All that’s left, once there’s a FastCGI daemon to point at, is to direct nginx to hand PHP requests off to it. To do that we go to the relevant server{} block for the virtual host we’re wanting to configure and add a new location{} block. For example:

location ~ \.php$ {

 include fastcgi.conf;
 fastcgi_pass unix:/run/php/php7.0-fpm.sock;

}

This stanza is pretty straight forward:

  • The criteria for our location is a regexp pattern (~) that matches any filename that ends in a literal .php extension.
  • We include the default FastCGI parameters (equivalent to environmental variables in regular CGI) as specified in /etc/nginx/fastcgi.conf which does a good job of specifying what a standard configuration.
  • Finally, we instruct the FastCGI module in nginx to pass the request off to the process listening on the specified socket.

At this point you should be able to reload your server configuration with a nginx -t (to validate syntax) and then a nginx -s reload (to perform the actual reload).

Your PHP-FPM configuration should now be functional at a bare minimal level now. To test you may try to create a phpinfo(); script inside of the virtual host’s document root and accessing it by way of a web browser. Depending on the application you’re running you may need to do more but the above is sufficient just to get nginx and PHP-FPM communicating properly.

Considerations With FastCGI

In addition to the above, you may need to tweak nginx to work on your given system. Some key points to bear in mind:

  • Exempt the location(s) of any upload directories your application has. This prevents someone from uploading a PHP file and executing it. Ideally your application should filter out these sorts of files but the modern standard is “defense in depth” so you should have a fail safe in your nginx configuration. For example, of a Drupal site you might change the location{} block so that it reads:
    location ~ \.php$ {
    
      include fastcgi.conf;
    
      if ($uri !~ /sites/default/files){
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
      }
    
    }
  • It may be useful to set HTTP_PROXY to an empty string (if you don’t need a value) to avoid a class of attacks where application and/or library developers put too much faith in $_SERVER values. This sets it to a static value before passing control over to PHP. This prevents the client from providing a value for this header.
  • If you application actually uses PATH_INFO1 (for example neither Drupal or WordPress do) then you’ll need to set fastcgi_split_path_info so that the value for PATH_INFO can be separated from the file path in the URI. To figure out if you need that, a PATH_INFO would be a URL such as /index.php/index/list where the index.php portion is the script to be ran and /index/list is some resource identifier that that application uses. If you don’t see that during your application’s normal functioning, then it’s best to leave it alone so that the path in the URL doesn’t get changed unnecessarily.

Further Reading

Citations

  1. RFC 3875 — Common Gateway Interface spec Section 4.1.5 []