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}'