PHP-FPM Plugin

Monitor PHP-FPM (FastCGI Process Manager) with comprehensive metrics covering process pools, request performance, slow requests, and resource usage.

Overview

The PHP-FPM plugin collects detailed metrics from PHP-FPM's status page including:

  • Pool Status - Pool name, process manager type, start time
  • Process Metrics - Active, idle, total processes
  • Request Statistics - Accepted connections, request rate, slow requests
  • Queue Metrics - Listen queue length, max listen queue
  • Process Lifecycle - Max children reached, process limits
  • Performance - Requests per process, average request duration

Requirements

PHP-FPM Version

  • Minimum: PHP 7.4
  • Recommended: PHP 8.1 or later
  • Tested with: PHP 7.4, 8.0, 8.1, 8.2, 8.3

PHP-FPM Configuration

Status page must be enabled in PHP-FPM pool configuration.

Python Dependencies

No additional Python packages required - uses standard library urllib.

Configuration

Basic Configuration

plugins:
  php_fpm:
    enabled: true
    status_url: http://127.0.0.1:9000/status?json

With Custom Socket

plugins:
  php_fpm:
    enabled: true
    status_url: http://unix:/run/php/php8.1-fpm.sock:/status?json

HTTP via Nginx/Apache

plugins:
  php_fpm:
    enabled: true
    status_url: http://127.0.0.1:8080/fpm-status?json

All Configuration Options

plugins:
  php_fpm:
    enabled: true                               # Enable/disable plugin
    status_url: http://127.0.0.1/status?json    # Status URL
    timeout: 10                                 # Request timeout (seconds)
    format: json                                # Response format (json/text)

Environment Variables

Configuration can be overridden with environment variables:

export PHP_FPM_STATUS_URL="http://127.0.0.1:8080/status?json"

PHP-FPM Setup

Enable Status Page

Edit PHP-FPM pool configuration:

Ubuntu/Debian:

sudo nano /etc/php/8.1/fpm/pool.d/www.conf

CentOS/RHEL:

sudo nano /etc/php-fpm.d/www.conf

Enable status page:

; Enable status page
pm.status_path = /status

Optional - Enable ping page:

; Enable ping endpoint
ping.path = /ping
ping.response = pong

Restart PHP-FPM:

# Ubuntu/Debian
sudo systemctl restart php8.1-fpm

# CentOS/RHEL
sudo systemctl restart php-fpm

Nginx Configuration

Method 1: FastCGI via Socket

server {
    listen 127.0.0.1:8080;

    location ~ ^/(status|ping)$ {
        access_log off;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Security: only allow from localhost
        allow 127.0.0.1;
        deny all;
    }
}

Method 2: FastCGI via TCP

server {
    listen 127.0.0.1:8080;

    location ~ ^/(status|ping)$ {
        access_log off;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Reload Nginx:

sudo nginx -t && sudo systemctl reload nginx

Apache Configuration

Enable mod_proxy_fcgi:

sudo a2enmod proxy_fcgi
sudo systemctl restart apache2

Configure status endpoint:

<VirtualHost 127.0.0.1:8080>
    <LocationMatch "^/(status|ping)$">
        ProxyPass "unix:/run/php/php8.1-fpm.sock|fcgi://localhost/"
        Require local
    </LocationMatch>
</VirtualHost>

Security Considerations

Restrict access to localhost only:

Nginx:

location /status {
    allow 127.0.0.1;
    allow ::1;
    deny all;

    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    include fastcgi_params;
}

Apache:

<Location /status>
    Require local
</Location>

PHP-FPM pool config:

; Restrict allowed clients (if using TCP)
listen.allowed_clients = 127.0.0.1

Collected Metrics

Metric Description Unit Type
pool Pool name (e.g., "www") String Info
process_manager PM type (static/dynamic/ondemand) String Info
start_time Pool start timestamp Unix time Gauge
uptime_seconds Seconds since start Seconds Gauge
accepted_connections Total accepted connections Count Counter
listen_queue Current listen queue length Count Gauge
max_listen_queue Max listen queue since start Count Gauge
listen_queue_length Listen queue size limit Count Gauge
idle_processes Idle processes Count Gauge
active_processes Active processes Count Gauge
total_processes Total processes Count Gauge
max_active_processes Max active since start Count Gauge
max_children_reached Times max children limit hit Count Counter
slow_requests Slow requests count Count Counter

Dashboard Metrics

The StatusRadar dashboard displays:

Overview Card

  • Active Processes - Current active workers
  • Idle Processes - Available workers
  • Listen Queue - Queued requests
  • Slow Requests - Performance issues

Process Pool Chart

  • Total processes over time
  • Active vs idle processes
  • Process manager efficiency

Queue Metrics Chart

  • Listen queue length
  • Max queue reached
  • Queue saturation

Request Rate Chart

  • Accepted connections rate
  • Requests per process
  • Slow request rate

Process Limit Chart

  • Max children reached events
  • Process pool saturation
  • Capacity planning

Installation

Quick Install

PLUGINS='php_fpm' \
TOKEN='your-agent-token' \
PHP_FPM_STATUS_URL='http://127.0.0.1:8080/status?json' \
bash -c "$(curl -sL https://statusradar.dev/install-agent.sh)"

Install on Existing Agent

  1. Enable PHP-FPM status (see PHP-FPM Setup above)

  2. Configure agent:

    sudo nano /opt/statusradar/config/agent.yaml

    Add:

    plugins:
      php_fpm:
        enabled: true
        status_url: http://127.0.0.1:8080/status?json
  3. Restart agent:

    sudo systemctl restart statusradar-agent
  4. Verify:

    sudo journalctl -u statusradar-agent -n 50 --no-pager | grep php_fpm

    Expected:

    INFO: Plugin php_fpm: Metrics collected successfully
    INFO: Plugin php_fpm: Pool www, 5 active, 10 idle processes

Testing

Manual Plugin Test

cd /opt/statusradar
python3 plugins/php_fpm_plugin.py

Expected Output:

Plugin: php_fpm
Enabled: True
Available: True

Collecting metrics...
{
  "pool": "www",
  "process_manager": "dynamic",
  "start_time": 1697457600,
  "uptime_seconds": 86400,
  "accepted_connections": 123456,
  "listen_queue": 0,
  "max_listen_queue": 5,
  "listen_queue_length": 128,
  "idle_processes": 10,
  "active_processes": 5,
  "total_processes": 15,
  "max_active_processes": 12,
  "max_children_reached": 2,
  "slow_requests": 15
}

Test Status Endpoint

JSON format (recommended):

curl http://127.0.0.1:8080/status?json

Text format:

curl http://127.0.0.1:8080/status?full

Full process list:

curl http://127.0.0.1:8080/status?full&json

Test Ping Endpoint

curl http://127.0.0.1:8080/ping

# Expected: pong

Troubleshooting

Plugin Not Collecting Metrics

Check 1: Is PHP-FPM running?

sudo systemctl status php8.1-fpm  # Ubuntu/Debian
sudo systemctl status php-fpm     # CentOS/RHEL

Check 2: Is status page enabled?

# Check pool configuration
grep "pm.status_path" /etc/php/8.1/fpm/pool.d/www.conf

# Should show: pm.status_path = /status

Check 3: Is status endpoint accessible?

curl http://127.0.0.1:8080/status?json

# If connection refused, check Nginx/Apache config
# If 404, PHP-FPM status not enabled

Check 4: Check agent logs

sudo journalctl -u statusradar-agent -n 100 --no-pager | grep php_fpm

Common Errors

"Connection refused"

Error:

ERROR: Plugin php_fpm: Connection refused

Causes:

  1. Nginx/Apache not configured
  2. Wrong status_url
  3. PHP-FPM not listening on expected socket/port

Solution:

# Check PHP-FPM socket
sudo ls -la /run/php/

# Check Nginx is running
sudo systemctl status nginx

# Test status URL manually
curl -v http://127.0.0.1:8080/status?json

"404 Not Found"

Error:

ERROR: Plugin php_fpm: HTTP 404 - status page not found

Causes:

  1. pm.status_path not set in pool config
  2. Nginx/Apache not configured to pass status requests
  3. Wrong URL path

Solution:

1. Enable in PHP-FPM pool:

pm.status_path = /status

2. Configure Nginx:

location /status {
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    include fastcgi_params;
}

3. Restart services:

sudo systemctl restart php8.1-fpm nginx

"Access denied"

Error:

ERROR: Plugin php_fpm: HTTP 403 - Access forbidden

Cause: IP restrictions blocking access

Solution:

location /status {
    allow 127.0.0.1;
    allow ::1;
    deny all;

    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    include fastcgi_params;
}

"Invalid JSON response"

Error:

ERROR: Plugin php_fpm: Cannot parse JSON response

Cause: Missing ?json parameter in URL

Solution:

plugins:
  php_fpm:
    status_url: http://127.0.0.1:8080/status?json  # Add ?json

Performance Impact

On PHP-FPM

Minimal impact:

  • Status endpoint returns pre-calculated statistics
  • No PHP script execution
  • Response time: < 1ms

Benchmark:

  • Overhead: < 0.001% CPU
  • No measurable performance degradation

On Agent

Resource usage:

  • Memory: +8 MB
  • CPU: +2% during collection
  • Network: +1 KB per collection

Collection time: < 0.1 seconds

Use Cases

1. Process Pool Monitoring

Monitor:

  • Active vs idle processes
  • Process pool saturation
  • Process manager efficiency

Alert on:

  • Active processes > 90% of max
  • No idle processes (saturation)
  • Max children limit reached frequently

2. Queue Saturation

Monitor:

  • Listen queue length
  • Max listen queue reached
  • Queue vs queue limit

Alert on:

  • Listen queue > 0 (backlog building)
  • Max queue size reached
  • Queue approaching limit

3. Slow Request Detection

Monitor:

  • Slow request count
  • Slow request rate
  • Slow request threshold

Alert on:

  • Slow requests increasing
  • Slow request rate > 1% of total

4. Capacity Planning

Monitor:

  • Max active processes trend
  • Max children reached count
  • Process turnover rate

Use for:

  • Determining optimal pm.max_children
  • Scaling decisions
  • Performance tuning

Best Practices

1. Choose Correct Process Manager

Static PM:

pm = static
pm.max_children = 50
  • Pros: Predictable resource usage
  • Cons: Always uses max memory
  • Best for: Dedicated PHP servers, consistent load

Dynamic PM (recommended):

pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
  • Pros: Adapts to load, efficient memory usage
  • Cons: Slight startup delay
  • Best for: Variable load, shared servers

Ondemand PM:

pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
  • Pros: Minimal memory when idle
  • Cons: Slow initial response
  • Best for: Low traffic sites, development

2. Calculate pm.max_children

Formula:

pm.max_children = (Available RAM) / (Average PHP process size)

Example:
Available RAM: 2GB = 2048MB
PHP process: 50MB (check with: ps aux | grep php-fpm)
pm.max_children = 2048 / 50 = 40

Always leave headroom:

Recommended: 80% of calculated value
pm.max_children = 40 × 0.8 = 32

3. Tune Dynamic PM Settings

Start Servers:

pm.start_servers = (min_spare + max_spare) / 2

Min/Max Spare:

pm.min_spare_servers = pm.max_children × 0.1
pm.max_spare_servers = pm.max_children × 0.2

Example for pm.max_children = 50:

pm.start_servers = 8
pm.min_spare_servers = 5
pm.max_spare_servers = 10

4. Enable Slow Request Logging

; Log slow requests
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s

Monitor slow.log for bottlenecks:

sudo tail -f /var/log/php-fpm/slow.log

5. Set Request Limits

; Terminate long-running requests
request_terminate_timeout = 30s

; Restart workers after N requests (prevent memory leaks)
pm.max_requests = 500

6. Monitor Status Page Regularly

Set up alerts for:

  • listen_queue > 0 (backlog)
  • max_children_reached > 0 (saturation)
  • slow_requests increasing
  • idle_processes < 2 (no capacity)

PHP-FPM Performance Tuning

Optimize Process Manager

For high traffic:

pm = static
pm.max_children = 100

For variable traffic:

pm = dynamic
pm.max_children = 50
pm.min_spare_servers = 10
pm.max_spare_servers = 20

Optimize Request Handling

Disable access log (status endpoint):

location /status {
    access_log off;  # Reduce I/O
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

Enable OPcache:

opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2

Memory Optimization

Reduce memory_limit if possible:

memory_limit = 128M  # Adjust based on needs

Monitor actual usage:

ps aux | grep php-fpm | awk '{sum+=$6} END {print sum/1024 " MB"}'

Advanced Configuration

Multiple PHP-FPM Pools

Monitor multiple pools:

plugins:
  php_fpm_www:
    enabled: true
    status_url: http://127.0.0.1:8080/status?json

  php_fpm_api:
    enabled: true
    status_url: http://127.0.0.1:8081/api-status?json

Configure different pools:

; /etc/php/8.1/fpm/pool.d/www.conf
[www]
pm.status_path = /status

; /etc/php/8.1/fpm/pool.d/api.conf
[api]
pm.status_path = /api-status

Unix Socket Monitoring

Direct socket access:

plugins:
  php_fpm:
    enabled: true
    status_url: http://unix:/run/php/php8.1-fpm.sock:/status?json

Note: Requires HTTP client support for Unix sockets (not all clients support this).

Docker Container

Monitor PHP-FPM in Docker:

plugins:
  php_fpm:
    enabled: true
    status_url: http://php-fpm:9000/status?json

Expose status via TCP:

; PHP-FPM config in Docker
listen = 0.0.0.0:9000
pm.status_path = /status

Example Configurations

Nginx + PHP-FPM Socket

plugins:
  php_fpm:
    enabled: true
    status_url: http://127.0.0.1:8080/status?json

Nginx config:

server {
    listen 127.0.0.1:8080;

    location /status {
        access_log off;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        allow 127.0.0.1;
        deny all;
    }
}

Apache + PHP-FPM

plugins:
  php_fpm:
    enabled: true
    status_url: http://127.0.0.1:8080/fpm-status?json

Apache config:

<VirtualHost 127.0.0.1:8080>
    <Location /fpm-status>
        ProxyPass "unix:/run/php/php8.1-fpm.sock|fcgi://localhost/"
        Require local
    </Location>
</VirtualHost>

Multiple PHP Versions

plugins:
  php74:
    enabled: true
    status_url: http://127.0.0.1:8074/status?json

  php81:
    enabled: true
    status_url: http://127.0.0.1:8081/status?json

Limitations

Current Limitations

  1. No per-request details - Only pool-wide statistics
  2. No memory per process - Use full status for detailed metrics
  3. No OPcache metrics - Monitor separately via PHP script

Scalability

Tested with:

  • Pools with 200+ processes
  • Handling 10,000+ req/sec
  • Multiple pools per server

Performance:

  • Status endpoint response constant regardless of pool size
  • No impact on PHP-FPM performance

Next Steps