How to Access PHP-FPM Status Page Directly (Bypassing Apache) for Monitoring and Debugging


13 views

When working with legacy PHP installations (particularly PHP 5.3.3/5.3.4), you might encounter issues accessing the PHP-FPM status page through Apache due to bug #52674. This becomes particularly problematic when:

  • Running older Ubuntu/Debian systems (like Ubuntu 10.10)
  • Unable to upgrade PHP due to compatibility constraints
  • Needing real-time monitoring of PHP-FPM processes

Here are three reliable ways to access the status page when Apache proxying fails:

Method 1: Using cURL with FastCGI Protocol

SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /var/run/php5-fpm.sock | grep -E "pool|accepted conn"

Method 2: Direct Socket Communication

Create a PHP script to talk directly to the FPM socket:

<?php
$socket = fsockopen('unix:///var/run/php5-fpm.sock');
fwrite($socket, "GET /status HTTP/1.1\r\nHost: localhost\r\n\r\n");
while (!feof($socket)) {
    echo fgets($socket, 4096);
}
fclose($socket);
?>

Method 3: Using netcat

echo -e "GET /status HTTP/1.0\r\n\r\n" | nc -U /var/run/php5-fpm.sock

The status page returns valuable metrics in plain text format:

pool:                 www
process manager:      dynamic
start time:           01/Jan/2023:00:00:00 +0000
accepted conn:        1234
listen queue:         0
max listen queue:     5
listen queue len:     128
idle processes:       7
active processes:     3
total processes:      10
max active processes: 15
max children reached: 2

For regular monitoring, create a bash script to parse the status output:

#!/bin/bash
status=$(SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET \
         cgi-fcgi -bind -connect /var/run/php5-fpm.sock)

active=$(echo "$status" | awk '/^active processes:/ {print $3}')
total=$(echo "$status" | awk '/^total processes:/ {print $3}')
echo "Active: $active/$total processes"
  • Ensure your FPM socket has proper permissions (usually www-data:www-data)
  • Consider implementing IP-based restrictions for status access
  • The status page should never be exposed publicly

After hitting PHP Bug #52674 on Ubuntu 10.10 (PHP 5.3.3-1ubuntu9.5), I discovered the standard Apache+PHP-FPM status page monitoring breaks completely. The usual /status?json or /ping endpoints return 403 Forbidden due to a FastCGI protocol handling bug.

We can bypass Apache completely by communicating directly with PHP-FPM's Unix socket or TCP port. Here's a Python script that implements the FastCGI protocol minimally:


import socket
import json

FCGI_VERSION = 1
FCGI_BEGIN_REQUEST = 1
FCGI_PARAMS = 4
FCGI_STDIN = 5
FCGI_STDOUT = 6
FCGI_END_REQUEST = 3

def make_header(req_type, req_id, content_len, padding_len=0):
    return bytes([
        FCGI_VERSION,
        req_type,
        (req_id >> 8) & 0xff,
        req_id & 0xff,
        (content_len >> 8) & 0xff,
        content_len & 0xff,
        padding_len,
        0
    ])

def get_status(socket_path='/var/run/php5-fpm.sock'):
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.connect(socket_path)
    
    # Begin request
    s.send(make_header(FCGI_BEGIN_REQUEST, 1, 8))
    s.send(b'\x00\x01\x00\x00\x00\x00\x00\x00')  # Role responder
    
    # Send params
    params = {
        'SCRIPT_FILENAME': '/status',
        'SCRIPT_NAME': '/status',
        'REQUEST_METHOD': 'GET'
    }
    param_data = b''
    for k,v in params.items():
        param_data += bytes([len(k)]) + bytes([len(v)]) + k.encode() + v.encode()
    
    s.send(make_header(FCGI_PARAMS, 1, len(param_data)))
    s.send(param_data)
    
    # End params
    s.send(make_header(FCGI_PARAMS, 1, 0))
    
    # Read response
    response = b''
    while True:
        header = s.recv(8)
        if not header: break
        
        version, type_, req_id1, req_id0, len1, len0, pad_len, _ = header
        content_len = (len1 << 8) + len0
        content = s.recv(content_len)
        
        if type_ == FCGI_STDOUT:
            response += content
        
        if pad_len: s.recv(pad_len)  # Skip padding
    
    return response.decode()

For quick CLI checks, these approaches work without coding:

Method 1: cgi-fcgi binary


SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /var/run/php5-fpm.sock

Method 2: Netcat for TCP FPM


echo -en "GET /status HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc 127.0.0.1 9000

The raw output needs parsing for monitoring systems. Here's a jq command to process JSON output:


curl -s "http://localhost/status?json" | jq '{
    active_processes: .active_processes,
    idle_processes: .idle_processes,
    total_requests: .total_requests,
    slow_requests: .slow_requests
}'

When using the socket method, first extract the JSON portion:


python3 get_fpm_status.py | awk 'NR==1, /^{/ {next} /^}/ {print} {print}'