Skip to main content

PHP-FPM Pool Configuration

Learning Focus

Leave this lesson with a working understanding of php-fpm pool configuration that you can apply immediately in production.

A PHP-FPM pool is a group of worker processes that handle PHP requests. You can have one pool for all sites or separate pools per site. This page covers pool configuration in depth.


Pool Config File Location

# Debian/Ubuntu
/etc/php/8.4/fpm/pool.d/www.conf # Default pool
/etc/php/8.4/fpm/pool.d/site2.conf # Add new files for per-site pools

# RHEL/AlmaLinux
/etc/php-fpm.d/www.conf

# After editing — reload FPM
sudo systemctl reload php8.4-fpm

Annotated Pool Config

/etc/php/8.4/fpm/pool.d/www.conf
; ============================================================
; POOL IDENTITY
; ============================================================
[www]

; User and group the worker processes run as
; Must match the user Nginx uses (www-data on Ubuntu)
user = www-data
group = www-data

; ============================================================
; SOCKET / LISTEN ADDRESS
; ============================================================

; UNIX socket (preferred — faster than TCP)
listen = /run/php/php8.4-fpm.sock

; TCP alternative (use if FPM is on a different server)
; listen = 127.0.0.1:9000

; Socket file ownership — must be readable by Nginx user
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; ============================================================
; PROCESS MANAGER (PM) MODE
; ============================================================

; pm = static — fixed number of workers, best for dedicated high-load servers
; pm = dynamic — workers scale between min and max, best for variable load
; pm = ondemand — workers spawned on demand, killed when idle (low-RAM servers)

pm = dynamic

; ---- dynamic settings ----

; Max total workers (hard cap)
pm.max_children = 20

; Workers always running (minimum)
pm.start_servers = 5

; Min idle workers (spawn more if below this)
pm.min_spare_servers = 5

; Max idle workers (kill extras above this)
pm.max_spare_servers = 10

; Max requests per worker before it restarts (prevents memory leaks)
pm.max_requests = 500

; ============================================================
; STATUS AND PING ENDPOINTS
; ============================================================

; Enable /status for monitoring (restrict in Nginx)
pm.status_path = /fpm-status

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

; ============================================================
; LOGGING
; ============================================================

; Log slow requests (taking more than N seconds)
slowlog = /var/log/php8.4-fpm-slow.log
request_slowlog_timeout = 5s

; Log format for access log
; access.log = /var/log/php8.4-fpm-access.log

; ============================================================
; SECURITY
; ============================================================

; Restrict PHP to only access files under these paths
; php_admin_value[open_basedir] = /var/www/example.com/:/tmp/:/usr/share/php/

; ============================================================
; PHP INI OVERRIDES (per-pool)
; ============================================================
php_admin_value[error_log] = /var/log/php8.4-fpm-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/sessions/
php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache/

PM Mode Comparison

ModeWorkersRAM UsageBest For
staticFixed (always max_children)High (always running)Dedicated high-load servers
dynamicScales between min/maxMedium (scales with load)Most production sites
ondemandSpawned on request, killed when idleLowestLow-traffic sites, multiple sites on limited RAM

Sizing pm.max_children

Rule of thumb: max_children = (Available RAM) / (Average PHP worker RSS)

# Check average PHP-FPM worker memory (RSS)
ps aux | grep php-fpm | grep -v grep | awk '{print $6}' | awk '{sum+=$1; count++} END {printf "Avg: %.0f KB = %.0f MB\n", sum/count, sum/count/1024}'

# Example: if each worker uses 30 MB and you have 2 GB RAM available for PHP:
# max_children = 2048 / 30 ≈ 68

Per-Site Pools (Isolation)

Create separate pools per site for resource isolation and security:

# Create a new pool for site2
sudo tee /etc/php/8.4/fpm/pool.d/site2.conf > /dev/null <<'EOF'
[site2]
user = www-data
group = www-data

listen = /run/php/php8.4-site2.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 200

; Open basedir restriction for security
php_admin_value[open_basedir] = /var/www/site2.com/:/tmp/
EOF

# Reload FPM to load new pool
sudo systemctl reload php8.4-fpm

# Verify socket was created
ls -la /run/php/php8.4-site2.sock

Then in Nginx, reference the pool-specific socket:

# site2's server block
location ~ \.php$ {
include fastcgi.conf;
fastcgi_pass unix:/run/php/php8.4-site2.sock;
}

Monitoring and Diagnostics

# Check FPM status (if pm.status_path configured)
curl http://localhost/fpm-status -H "Host: example.com"

# Check health ping
curl http://localhost/fpm-ping -H "Host: example.com"

# Live slow log
sudo tail -f /var/log/php8.4-fpm-slow.log

# Count running workers
ps aux | grep php-fpm | grep -v grep | wc -l

# Memory used by all FPM workers
ps aux | grep php-fpm | grep -v grep | awk '{sum+=$6} END {printf "Total: %.0f MB\n", sum/1024}'

# Reload pool config (after changes)
sudo systemctl reload php8.4-fpm

# Full restart (if reload doesn't apply)
sudo systemctl restart php8.4-fpm

Hands-On Practice

# Verify Nginx is running
sudo systemctl status nginx

# Test config syntax
sudo nginx -t

# Reload without downtime
sudo nginx -s reload

# Check error log
sudo tail -20 /var/log/nginx/error.log

Common Pitfalls

PitfallWhat happensFix
Editing config without reloadingChanges not appliedsudo nginx -s reload after every edit
Not running nginx -t firstReload breaks with syntax errorAlways test syntax before reloading
Wrong socket path for PHP-FPM502 Bad Gatewayls /run/php/ and verify the exact socket filename

What's Next

  • Continue to the next lesson in this module, or go to the module index for an overview.
  • Use the Cheatsheets for quick CLI reference.