PHP-FPM (FastCGI Process Manager) is the recommended way to run PHP behind a web server. Unlike the older Apache mod_php, which embeds a PHP interpreter in each Apache worker process, PHP-FPM runs as a separate service that manages a pool of PHP worker processes. Nginx communicates with PHP-FPM via a Unix socket or TCP socket using the FastCGI protocol. This architecture separates web server and PHP concerns cleanly, allows Nginx to serve static files at full speed without involving PHP, and enables per-site PHP process pool configuration including separate users, memory limits, and process counts per application. This guide covers configuring PHP-FPM pool settings for production on RHEL 9 and integrating it with Nginx for a web application deployment.

Prerequisites

  • PHP 8.3 (with php-fpm) and Nginx installed on RHEL 9

Step 1 — Configure the PHP-FPM Pool

# /etc/php-fpm.d/www.conf — production pool configuration
[www]
user = nginx
group = nginx

; Use Unix socket for better performance than TCP
listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

; Dynamic process management
pm = dynamic
pm.max_children = 50          ; Maximum PHP workers (tune based on RAM: ~25 MB per worker)
pm.start_servers = 10         ; Workers at startup
pm.min_spare_servers = 5      ; Minimum idle workers
pm.max_spare_servers = 20     ; Maximum idle workers
pm.max_requests = 500         ; Restart worker after 500 requests (prevents memory leaks)

; Logging
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 10s
access.log = /var/log/php-fpm/www-access.log

; Security: clear environment
clear_env = yes
env[PATH] = /usr/local/bin:/usr/bin:/bin
systemctl restart php-fpm
ls -la /run/php-fpm/www.sock

Step 2 — Configure Nginx to Use PHP-FPM

# /etc/nginx/conf.d/webapp.conf
server {
    listen 80;
    server_name webapp.example.com;
    root /var/www/webapp/public;
    index index.php index.html;

    # Serve static files directly — no PHP involved
    location ~* .(jpg|jpeg|gif|png|css|js|ico|svg|woff2|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    # PHP application entry point
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;
        fastcgi_read_timeout 300;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    # Deny access to hidden files
    location ~ /. {
        deny all;
    }
}
nginx -t && systemctl reload nginx

Step 3 — Create a Separate Pool for Each Application

# Create a dedicated pool for an application for process isolation
# /etc/php-fpm.d/myapp.conf
[myapp]
user = myapp
group = myapp
listen = /run/php-fpm/myapp.sock
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 10

Step 4 — Tune pm.max_children Based on Server RAM

# Calculate the maximum safe number of PHP workers
# Formula: (Total RAM - OS overhead) / average PHP worker RAM

# Check current average PHP worker memory usage
ps --no-headers -o rss -C php-fpm | awk '{sum+=$1} END {print "Average RSS: " sum/NR/1024 " MB"}'

# Example: 4 GB server, 500 MB OS overhead, 25 MB per worker
# (4096 - 512) / 25 = ~144 max_children maximum
# Use 70-80% of that as pm.max_children: ~100

Step 5 — Enable PHP-FPM Status Page

# In /etc/php-fpm.d/www.conf:
pm.status_path = /fpm-status

# In Nginx server block (restrict to localhost):
location /fpm-status {
    fastcgi_pass unix:/run/php-fpm/www.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    allow 127.0.0.1;
    deny all;
}

# Check status:
curl -s http://127.0.0.1/fpm-status

Conclusion

PHP-FPM with Nginx on RHEL 9 is the performance-standard stack for PHP web applications. The most impactful tuning parameter is pm.max_children — too low causes request queuing under load, too high causes swap thrashing that degrades all performance. Use the PHP-FPM status page (/fpm-status) to monitor active and idle workers in real time and adjust pool settings based on actual traffic patterns.

Next steps: How to Harden PHP on RHEL 9, How to Install Laravel with Nginx on RHEL 9, and How to Install Nginx on RHEL 9.