Skip to content

PHP-FPM Configuration

PHP-FPM Configuration

Before using fcgx to monitor PHP-FPM, you need to enable the status endpoints in your PHP-FPM configuration.

Basic Status Configuration

Edit your pool configuration (/etc/php/8.x/fpm/pool.d/www.conf):

; Enable status endpoint
pm.status_path = /status

; Enable ping endpoint for health checks
ping.path = /ping
ping.response = pong

Restart PHP-FPM after changes:

sudo systemctl restart php8.x-fpm

Admin Socket for High-Load Sites

Critical for production: Under high load, all PHP-FPM workers may be busy processing requests. This makes the status endpoint unresponsive - exactly when you need it most.

The Problem

Main pool (busy)          Status request
┌─────────────────┐       ┌─────────────┐
│ Worker 1: busy  │       │             │
│ Worker 2: busy  │  ───► │  BLOCKED    │
│ Worker 3: busy  │       │  (waiting)  │
│ Worker 4: busy  │       │             │
└─────────────────┘       └─────────────┘

When all workers are handling long-running requests, status queries queue behind them.

The Solution: pm.status_listen

PHP-FPM 7.4+ provides pm.status_listen - a dedicated socket for status requests:

; Main application socket
listen = /var/run/php-fpm.sock

; Dedicated admin socket for monitoring (separate invisible pool)
pm.status_listen = /var/run/php-fpm-status.sock

; Status endpoints
pm.status_path = /status
ping.path = /ping
ping.response = pong
Main pool (busy)          Admin socket (always available)
┌─────────────────┐       ┌─────────────────┐
│ Worker 1: busy  │       │                 │
│ Worker 2: busy  │       │  Status: OK     │  ◄── Monitoring
│ Worker 3: busy  │       │  Response: 2ms  │
│ Worker 4: busy  │       │                 │
└─────────────────┘       └─────────────────┘

The admin socket creates an invisible pool that handles status requests independently.

Using Admin Socket with fcgx

// Use the dedicated admin socket for monitoring
client, err := fcgx.DialContext(ctx, "unix", "/var/run/php-fpm-status.sock")
if err != nil {
    return err
}
defer client.Close()

params := map[string]string{
    "SCRIPT_NAME":     "/status",
    "SCRIPT_FILENAME": "/status",
    "QUERY_STRING":    "json&full",
}

// This will respond even when main pool is at 100% capacity
resp, err := client.Get(ctx, params)

Socket Permissions

Ensure your monitoring service can access the admin socket:

; In pool config
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Admin socket inherits same permissions
pm.status_listen = /var/run/php-fpm-status.sock

Or add your monitoring user to the www-data group:

sudo usermod -aG www-data monitoring-user

Complete Pool Configuration

A production-ready pool configuration for monitoring:

[www]
; Main socket for application traffic
listen = /var/run/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Process manager settings
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35

; === MONITORING CONFIGURATION ===

; Dedicated admin socket (always responsive)
pm.status_listen = /var/run/php-fpm-status.sock

; Status and health endpoints
pm.status_path = /status
ping.path = /ping
ping.response = pong

; Slow request logging (useful for debugging)
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s

; Request termination (prevents runaway processes)
request_terminate_timeout = 300s

Multiple Pools

For sites with multiple PHP-FPM pools, configure each with its own admin socket:

[www]
listen = /var/run/php-fpm-www.sock
pm.status_listen = /var/run/php-fpm-www-status.sock
pm.status_path = /status

[api]
listen = /var/run/php-fpm-api.sock
pm.status_listen = /var/run/php-fpm-api-status.sock
pm.status_path = /status

[admin]
listen = /var/run/php-fpm-admin.sock
pm.status_listen = /var/run/php-fpm-admin-status.sock
pm.status_path = /status

Monitor all pools:

pools := []struct {
    Name   string
    Socket string
}{
    {"www", "/var/run/php-fpm-www-status.sock"},
    {"api", "/var/run/php-fpm-api-status.sock"},
    {"admin", "/var/run/php-fpm-admin-status.sock"},
}

for _, pool := range pools {
    status, err := getPoolStatus(ctx, pool.Socket)
    if err != nil {
        log.Printf("[%s] Error: %v", pool.Name, err)
        continue
    }
    log.Printf("[%s] Active: %d, Idle: %d", pool.Name, status.ActiveProcesses, status.IdleProcesses)
}

TCP Sockets

For remote monitoring or containerized environments:

; TCP socket instead of Unix socket
listen = 127.0.0.1:9000

; Admin socket on different port
pm.status_listen = 127.0.0.1:9001
// Connect via TCP
client, err := fcgx.DialContext(ctx, "tcp", "127.0.0.1:9001")

Security note: Only bind to localhost or use firewall rules. Never expose PHP-FPM directly to the internet.

Status Output Formats

The status endpoint supports multiple output formats via query string:

Query String Format Use Case
(none) Plain text Human readable
?json JSON API consumption
?json&full JSON with processes Detailed monitoring
?xml XML Legacy systems
?html HTML Browser viewing

Verification

Test your configuration:

# Check PHP-FPM config syntax
php-fpm -t

# Verify sockets exist after restart
ls -la /var/run/php-fpm*.sock

# Test with fcgx (see Quickstart)

Next Steps