Directly connecting to PHP-FPM

This post was published 5 years, 3 months ago. Due to the rapidly evolving world of technology, some concepts may no longer be applicable.

When it comes to troubleshooting, it is ideal to be able to isolate each component of a system. In the case where multiple connected items are performing correctly, they can sometimes be grouped together – however, if one of these items is not functioning, diagnostics become much harder.

My typical web server stack includes:

  • Varnish as a caching proxy
  • Nginx as a web server
  • PHP-FPM for PHP via FastCGI

In the above setup, Varnish and nginx run on different ports, making it fairly easy to bypass Varnish and query nginx directly, however, it isn’t quite as easy to query PHP-FPM directly. Without being able to do so, it can be difficult to determine if a misconfiguration is on the nginx side or the PHP-FPM side.

Talking to your FastCGI server

(Some commands below are specific to CentOS/RHEL, but the basic ideas should be generally applicable)

Unlike many other services (e.g. SMTP servers), it is not possible to connect to a FastCGI server using telnet – the communication protocol is not plaintext.

Luckily though, one of the tools included with FastCGI is cgi-fcgi – a bridge from CGI to FastCGI, which we can use to query a FastCGI server directly, bypassing our web server.

On RHEL/CentOS, cgi-fcgi is included in the fcgi package, and can be installed with:

yum --enablerepo=epel install fcgi

The package is fairly small (84 kb installed) and is available from EPEL (currently v2.4.0-10.el6). cgi-fcgi installs to /usr/bin

A notable difference between FastCGI and most other servers is that parameters are set as environment variables. As such, the necessary environment variables must be set before cgi-fcgi is called if a successful page is to be returned.

To connect to a FastCGI server that is already running, we pass the --bind and --connect parameters, with the socket path (or address and port). For instance:

cgi-fcgi -bind -connect 127.0.0.1:9010

In its simplest form, we need to pass only the SCRIPT_FILENAME, SCRIPT_NAME, and REQUEST_METHOD to the application. More complex setups will require additional variables, especially DOCUMENT_ROOT and QUERY_STRING.

PHP-FPM can be configured to respond to pings – that is, it will serve a predefined response every time a particular path is queried. Pings must be enabled on a per-pool basis, from the php-fpm config, by adding (or uncommenting) the following line (you can change the path as desired):

ping.path = /ping

(The response can be set with ping.response = response – but will default to ‘pong’ if omitted).

For the changes to the config to be picked up, you must reload php-fpm:

service php-fpm reload

You can test this by running the following (again, modify the connect line to reference the socket on which PHP-FPM listens)

SCRIPT_NAME=/ping \
SCRIPT_FILENAME=/ping \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:PORT

A typical response may be:

X-Powered-By: PHP/5.3.9
Content-Type: text/plain
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store, must-revalidate, max-age=0

pong

The above can be modified to return a PHP page, by passing the correct SCRIPT_FILENAME, SCRIPT_NAME, QUERY_STRING, and DOCUMENT_ROOT.

Retrieving the PHP-FPM status page

The status page seems to be a minimally documented feature of PHP-FPM. It is a brief server-generated page (TEXT, JSON, HTML, or XML) which provides basic information about a pool. To enable it, add (or uncomment) the following in your php-fpm config, on a per-pool basis:

pm.status_path = /status

As with ping, you can modify the path that will return the status page. The path must begin with a slash (of course, it is good practice to not include a ‘php’ extension on it, as that would be needlessly confusing).

Note: since the status page will offer different formats depending on the query string, you must set the QUERY_STRING variable, or the page will not work. Valid query strings are json, xml, html – all other query strings will return a plain text version.

For example, using the default settings, the following will return plain text statistics (again, modify the connect line to reference the socket on which PHP-FPM listens):

SCRIPT_NAME=/status \
SCRIPT_FILENAME=/status \
QUERY_STRING= \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:PORT

Sample output:

X-Powered-By: PHP/5.3.9
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Content-Type: text/plain

pool: web1
process manager: dynamic
start time: 01/Feb/2012:20:49:44 -0500
start since: 1214
accepted conn: 5
listen queue: 0
max listen queue: 0
listen queue len: 128
idle processes: 1
active processes: 1
total processes: 2
max active processes: 1
max children reached: 0

For interest sake, I threw together a simple Perl script that could accept a path as an argument, set the basic variables, and call cgi-fcgi. Note, it does not set the DOCUMENT_ROOT, which may be needed for more complex scripts.

fcgi.pl:

#!/usr/bin/perl -w
use strict;
 
my $fpm = $ARGV[0];
my $url = $ARGV[1];
 
if (!defined $fpm || !defined $url ){
    print "Usage: $0 host:port|path/to/socket /path/to/file \n";
    exit 1;
}
 
if($url =~ /^((?:\/.*)?(\/[^?]*))(?:\?(.*))?$/) {
    $ENV{REQUEST_METHOD}='GET';
    $ENV{SCRIPT_FILENAME}= $1;
    $ENV{SCRIPT_NAME}= $2;
    $ENV{QUERY_STRING}= $3 // '';
}
 
system ('cgi-fcgi', '-bind', '-connect', $fpm);

To request the status page in JSON format, using the above, you would run:

./fcgi.pl 127.0.0.1:9010 /status?json

(There is a space between the socket and the path – they are two separate parameters)

The PHP-FPM Status Page

The best documentation of the status page that I have come across is in the php-fpm config file. As such, it takes a bit more effort than desirable to discern the meaning of some of the values. Here is my current understanding of them (PHP v5.3.9):

  • pool – the name of the pool that is listening on the connected socket, as defined in the php-fpm config.
  • process manager – the method used by the process manager to control the number of child processes – either dynamic or static – set on a per pool basis (in the php-fpm config) by the pm parameter.
  • start time – the date, time, and UTC offset corresponding to when the PHP-FPM server was started.
  • start since – the number of seconds that have elapsed since the PHP-FPM server was started (i.e. uptime).
  • accepted conn – the number of incoming requests that the PHP-FPM server has accepted; when a connection is accepted it is removed from the listen queue (displayed in real time).
  • listen queue – the current number of connections that have been initiated, but not yet accepted. If this value is non-zero it typically means that all the available server processes are currently busy, and there are no processes available to serve the next request. Raising pm.max_children (provided the server can handle it) should help keep this number low. This property follows from the fact that PHP-FPM listens via a socket (TCP or file based), and thus inherits some of the characteristics of sockets.
  • max listen queue – the maximum value the listen queue has reached since the server was started.
  • listen queue len – the upper limit on the number of connections that will be queued Once this limit is reached, subsequent connections will either be refused, or ignored. This value is set by the php-fpm per pool configuration option ‘listen.backlog‘, which defaults to -1 (unlimited). However, this value is also limited by the system (sysctl) value ‘net.core.somaxconn‘, which defaults to 128 on many Linux systems.
  • idle processes – the number of servers in the ‘waiting to process’ state (i.e. not currently serving a page). This value should fall between the pm.min_spare_servers and pm.max_spare_servers values when the process manager is dynamic. (updated once per second)
  • active processes – the number of servers current processing a page – the minimum is 1 (so even on a fully idle server, the result will be not read 0). (updated once per second)
  • total processes – the total number of server processes currently running; the sum of idle processes + active processes. If the process manager is static, this number will match pm.max_children. (updated once per second)
  • max active processes – the highest value that ‘active processes’ has reached since the php-fpm server started. This value should not exceed pm.max_children.
  • max children reached – the number of times that pm.max_children has been reached since the php-fpm server started (only applicable if the process manager is dynamic)

13 thoughts on “Directly connecting to PHP-FPM

  1. Really funny. thanks for the article.
    I saw that the status/ping is only for one pool at each time ?
    So I have to write a script to check all pools.
    Thanks

    • That is correct. If you want information on all the pools, you will need to query each one separately. You can also set it up to run through your webserver (e.g. by setting up location blocks in Nginx and querying each of those – but each is still done separately).

  2. here ->

    SCRIPT_NAME=/ping\
    SCRIPT_FILENAME=/ping\
    REQUEST_METHOD=GET \
    cgi-fcgi -bind -connect 127.0.0.1:PORT

    need a space before the backslash, like this

    SCRIPT_NAME=/ping \
    SCRIPT_FILENAME=/ping \
    REQUEST_METHOD=GET \
    cgi-fcgi -bind -connect 127.0.0.1:PORT

  3. Hey this is very necessary guide. But I need a some question.
    I have installed PHP 5.5.6 from source. Then I need a php-fpm status. But not webserver . How can I get status for php-fpm without webserver?
    Otherwise not nginx or apache.
    Php 5.5.6 install from source then not cgi-fcgi binary file.

    • Depending on what type of status you are looking for, there are a few options:
      a) Look for PID file (i.e. what most init scripts might do)
      b) See if the process is running (e.g. with ps)
      c) use cgi-fcgi (this is not webserver, and can run independently of Nginx, Apache, etc. – it is used to communicate over the FastCGI protocol). It is available as its own package on most systems (e.g. fcgi on CentOS)

Leave a Reply

Your email address will not be published. Required fields are marked *