🚀 PHP-FPM Pools & Nginx Configuration Guide

Complete Setup Guide for Secure Multi-Site Hosting

angelscript

📋 Table of Contents

1. Introduction to PHP-FPM Pools

PHP-FPM (FastCGI Process Manager) pools are a powerful feature that allows you to manage multiple PHP processes independently. Each pool operates as a separate entity with its own configuration, resources, and security boundaries. This architecture is essential for hosting multiple websites on a single server while maintaining isolation and security.

💡 What is PHP-FPM?

PHP-FPM is a process manager that works in conjunction with web servers like Nginx or Apache to handle PHP requests efficiently. It provides advanced features like adaptive process spawning, slow request logging, and graceful stop/start capabilities.

PHP-FPM Architecture Overview

Client Request
Nginx Web Server
PHP-FPM Master Process
Pool 1 (site1.com)
Pool 2 (site2.com)
Pool 3 (site3.com)

2. Understanding PHP Pools

What Are PHP Pools?

PHP pools allow you to create multiple sets of PHP processes, each dedicated to serving requests for specific sites, applications, or domains. Each pool operates independently with its own configuration settings and resource allocations.

Pool: site1.com

User: site1user

Socket: php8.3-fpm-site1.sock

Memory: 256M

Processes: 5-10

Pool: site2.com

User: site2user

Socket: php8.3-fpm-site2.sock

Memory: 128M

Processes: 3-8

Pool: site3.com

User: site3user

Socket: php8.3-fpm-site3.sock

Memory: 512M

Processes: 10-20

Advantages of PHP Pools

Feature Benefit Example
Isolation Each site runs in its own environment If site1.com is compromised, site2.com remains secure
Resource Control Allocate specific resources per pool High-traffic site gets more memory and processes
Custom Configuration Different PHP settings per site Enable specific extensions or memory limits
Security Contain security breaches Vulnerable plugin affects only one site
Performance Optimize each site independently Tune process management per workload

Disadvantages to Consider

⚠️ Potential Challenges

  • Resource Consumption: Multiple pools consume more memory than a single shared process
  • Complexity: Additional configuration and maintenance required
  • Cost: May require more powerful server hardware
  • Monitoring: Need to monitor each pool separately

3. Complete Setup Process

This comprehensive guide walks you through setting up PHP-FPM pools for multiple WordPress sites. Follow each step carefully to ensure proper configuration.

Step 1: Create User Account

1

Create a dedicated user for the site

Each site should have its own user account for isolation and security.

# Create new user (replace 'example' with your domain name)

sudo useradd example
angelscript

Step 2: Configure User Groups

2

Add users to appropriate groups

Ensure proper permissions between the web server, PHP-FPM, and file owner.

# Add www-data to the site user's group

sudo usermod -a -G example www-data

Add site user to www-data group (required for mail functionality)
sudo usermod -a -G www-data example

Add your non-root user to the site user's group
sudo usermod -a -G example $USER
angelscript

🔍 Understanding the Commands

  • -a: Append - adds user to group without removing from other groups
  • -G: Specifies the supplementary group to add the user to
  • www-data: The default Nginx web server user

Step 3: Create PHP-FPM Pool

3

Create and configure the pool file

Copy the default pool configuration and customize it for your site.

# Navigate to pool directory

cd /etc/php/8.3/fpm/pool.d/

Copy default pool configuration
sudo cp www.conf example.com.conf

Edit the new configuration
sudo nano example.com.conf
angelscript

Step 4: Configure Pool Settings

4

Customize pool configuration

Set the pool name, user, group, and socket path.

; Pool name

[example]

; Unix user/group of processes
user = example
group = example

; Socket path - IMPORTANT: Make this unique for each pool
listen = /run/php/php8.3-fpm-example.com.sock

; Resource limits
rlimit_files = 15000
rlimit_core = 100

; PHP configuration
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.example.com.log
php_admin_flag[log_errors] = on

; Optional: Set custom memory limit
; php_admin_value[memory_limit] = 256M

; Custom temp directories
php_admin_value[upload_tmp_dir] = /var/www/example.com/tmp/
php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/

; Security: Restrict file access
php_admin_value[open_basedir] = /var/www/example.com/public_html/:/var/www/example.com/tmp/

; Security: Disable dangerous functions
php_admin_value[disable_functions] = shell_exec,exec,passthru,system,proc_open,popen
angelscript

✅ Key Configuration Points

  • Pool Name: Should match your domain for easy identification
  • Socket Path: Must be unique for each pool
  • Resource Limits: Set based on site requirements
  • open_basedir: Restricts PHP file access to specific directories

Step 5: Create Log File

5

Set up PHP error logging

Create a dedicated log file for the pool with proper permissions.

# Create log file

sudo touch /var/log/fpm-php.example.com.log

Set ownership (user:www-data)
sudo chown example:www-data /var/log/fpm-php.example.com.log

Set permissions (read/write for owner and group)
sudo chmod 660 /var/log/fpm-php.example.com.log
angelscript

Step 6: Create Temp Directory

6

Create temporary upload directory

Essential for file uploads and temporary storage.

# Navigate to site root

cd /var/www/example.com/

Create temp directory
sudo mkdir tmp/

Set ownership
sudo chown example:example tmp/

Set permissions
sudo chmod 770 tmp/
angelscript

Step 7: Reload PHP-FPM

7

Apply the new configuration

Reload PHP-FPM to enable the new pool.

# Reload PHP-FPM service

sudo systemctl reload php8.3-fpm

Verify pool is running
sudo grep "listen = /" /etc/php/8.3/fpm/pool.d/example.com.conf
angelscript

Step 8: Configure Nginx

8

Update Nginx server block

Configure Nginx to use the new PHP-FPM pool socket.

# Edit Nginx site configuration

sudo nano /etc/nginx/sites-available/example.com.conf

Find the PHP processing location block and update:
location ~ .php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}

Test Nginx configuration
sudo nginx -t

Reload Nginx
sudo systemctl reload nginx
angelscript

Step 9: Set File Permissions

9

Configure proper ownership and permissions

Security is crucial - set appropriate file and directory permissions.

Standard Permissions (Development)

# Set ownership

sudo chown -R example:example /var/www/example.com/public_html/

Directories: 770 (rwxrwx---)
sudo find /var/www/example.com/public_html/ -type d -exec chmod 770 {} ;

Files: 660 (rw-rw----)
sudo find /var/www/example.com/public_html/ -type f -exec chmod 660 {} ;

Secure wp-config.php
sudo chmod 400 /var/www/example.com/public_html/wp-config.php

Hardened Permissions (Production)

# Base directories: 550 (r-xr-x---)

sudo find /var/www/example.com/public_html/ -type d -exec chmod 550 {} ;

Base files: 440 (r--r-----)
sudo find /var/www/example.com/public_html/ -type f -exec chmod 440 {} ;

wp-content writable: 770/660
sudo find /var/www/example.com/public_html/wp-content/ -type d -exec chmod 770 {} ;
sudo find /var/www/example.com/public_html/wp-content/ -type f -exec chmod 660 {} ;
angelscript

⚠️ Permission Guidelines

  • 770/660: Development - allows user and group full access
  • 550/440: Production - read-only for most files
  • wp-content: Always needs write permissions for uploads and updates
  • wp-config.php: Should be 400 (read-only for owner)

4. SSL/TLS Configuration

Secure your sites with Let's Encrypt SSL certificates and modern TLS configuration for A+ rating.

Install Certbot

# Update packages

sudo apt update

Install Certbot with Cloudflare DNS plugin
sudo apt install certbot python3-certbot-dns-cloudflare
oxygene

Obtain SSL Certificate

# Using webroot method

sudo certbot certonly --webroot
-w /var/www/example.com/public_html/
-d example.com
-d www.example.com

Generate Diffie-Hellman Parameters

# Create SSL directory

cd /etc/nginx/
sudo mkdir ssl/
cd ssl/

Generate DH parameters (takes several minutes)
sudo openssl dhparam -out dhparam.pem 2048

Create SSL Configuration Files

Site-Specific Certificate Configuration

# Create certificate configuration file

sudo nano /etc/nginx/ssl/ssl_certs_example.com.conf

Add these lines:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

Global SSL Configuration

# Create global SSL settings file

sudo nano /etc/nginx/ssl/ssl_all_sites.conf

Configuration for A+ SSL Labs rating
Updated: May 2025
SSL Session Management
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 180m;

TLS Protocols and Ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;

Strong cipher suite (single line in actual config)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;

DH parameters
ssl_dhparam /etc/nginx/ssl/dhparam.pem;

DNS resolver (Cloudflare)
resolver 1.1.1.1 1.0.0.1;
resolver_timeout 15s;

Disable session tickets
ssl_session_tickets off;

HSTS Header
add_header Strict-Transport-Security "max-age=31536000;" always;

HTTP/3 and QUIC
ssl_early_data on;
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
add_header x-quic 'H3';
quic_retry on;
angelscript

📝 Note on SSL Stapling

Let's Encrypt removed support for SSL stapling on May 7, 2025. The ssl_stapling and ssl_stapling_verify directives should be commented out or removed from your configuration.

Configure Nginx Server Block for HTTPS

# HTTP to HTTPS redirect

server {
listen 80;
server_name example.com www.example.com;

# 301 permanent redirect to HTTPS
return 301 https://example.com$request_uri;

}

HTTPS server block (first server with reuseport)
server {
listen 443 ssl;
http2 on;

awk
listen 443 quic reuseport;
http3 on;

server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php index.html;

# Include SSL configuration
include /etc/nginx/ssl/ssl_certs_example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;

# Rest of your configuration...

}

Additional HTTPS server (without reuseport)
server {
listen 443 ssl;
http2 on;

listen 443 quic;
http3 on;

# Configuration for www subdomain...

}
angelscript

✅ HTTP/3 and QUIC Support

  • First server: Use reuseport on the QUIC listen directive
  • Additional servers: Omit reuseport to avoid conflicts
  • Testing: Use browser DevTools Network tab to verify h3 protocol
  • Verification: Check https://http3check.net/

Testing HTTP/3

# Temporarily disable caching to test HTTP/3

Add to location / block:
add_header Cache-Control 'no-cache,no-store';

Test with curl
curl -I https://example.com

Remove the cache-control header after confirming HTTP/3 works

SSL Certificate Renewal

# Manual renewal

sudo certbot renew

Force renewal
sudo certbot renew --force-renewal

List all certificates
sudo certbot certificates

Delete certificate
sudo certbot delete

Automatic Renewal with Cron

# Edit root crontab

sudo crontab -e

Add these lines (renew on 14th and 28th at 1 AM)
00 1 14,28 * * certbot renew --force-renewal
00 2 14,28 * * systemctl reload nginx
angelscript

SSL Testing

🏆 Verify Your Configuration

  • SSL Labs: Test at https://www.ssllabs.com/ssltest/ (Target: A+ rating)
  • HTTP/3: Verify at https://http3check.net/
  • Browser DevTools: Check Network tab for "h3" protocol

5. Security Hardening

HTTP Security Headers

# Create security headers file

sudo nano /etc/nginx/includes/http_headers.conf

Add these headers:
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
add_header Permissions-Policy 'accelerometer=(), camera=(), clipboard-read=(), clipboard-write=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), fullscreen=(self "https://www.youtube.com")';

Include in your server block
include /etc/nginx/includes/http_headers.conf;

WordPress Security Directives

# Create WordPress security file

sudo nano /etc/nginx/includes/nginx_security_directives.conf

Disable favicon logging
location = /favicon.ico {
access_log off;
log_not_found off;
}

Deny access to sensitive files
location = /wp-config.php { deny all; }
location = /wp-admin/install.php { deny all; }
location ~* ^/(readme|license|licence).(txt|html)$ { deny all; }
location ~* .ini$ { deny all; }

Harden WP core
location ~* ^/wp-includes/[^/]+.php$ { deny all; }
location ~* ^/wp-includes/js/tinymce/langs/.+.php$ { deny all; }
location ~* ^/wp-includes/theme-compat/ { deny all; }

Prevent PHP execution in uploads
location ~* ^/wp-content/uploads/..(php[1-8]?|pht|phtml?|phps)$ { deny all; }
location ~ ^/wp-content/plugins/..(php[1-8]?|pht|phtml?|phps)$ { deny all; }
location ~ ^/wp-content/themes/.*.(php[1-8]?|pht|phtml?|phps)$ { deny all; }

Block development files
location ~* (composer.(json|lock)|package.json|yarn.lock|/vendor/|/node_modules/) { deny all; }

Block dangerous HTTP methods
if ($request_method ~* ^(TRACE|DELETE|TRACK)$) { return 403; }

Block vulnerability scanners
if ($http_user_agent ~* (nikto|sqlmap|masscan|nmap|dirbuster|acunetix|openvas)) { return 444; }
angelscript

🔒 Critical Security Measures

  • Always deny direct access to wp-config.php
  • Prevent PHP execution in uploads directory
  • Block access to development and dependency files
  • Disable dangerous HTTP methods
  • Consider blocking known vulnerability scanners

Allow Specific Plugin PHP Execution

Some plugins require PHP execution in the plugins directory. Here's how to allow it selectively:

# Allow PHP execution for a specific plugin file

location = /wp-content/plugins/specific-plugin/file.php {
allow all;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}

PHP Function Restrictions

# In your pool configuration file

php_admin_value[disable_functions] = shell_exec,opcache_get_configuration,opcache_get_status,disk_total_space,diskfreespace,dl,exec,passthru,pclose,pcntl_alarm,pcntl_exec,pcntl_fork,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,show_source,system,popen,posix_kill

Database Security

# Revoke all privileges

REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'hostname';

Grant only necessary privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'hostname';

For migrations or major updates, temporarily grant:
GRANT CREATE, ALTER, INDEX ON site_db.* TO 'site_user'@'hostname';

Apply changes
FLUSH PRIVILEGES;

WordPress Hardening

# Add to wp-config.php to disable file modifications

define('DISALLOW_FILE_MODS', true);
angelscript

⚠️ Important Notes

  • Setting DISALLOW_FILE_MODS prevents plugin/theme installation via admin panel
  • You'll need to install plugins/themes via SFTP or command line
  • This significantly improves security by preventing unauthorized modifications

6. Rate Limiting

Rate limiting helps protect your sites from brute force attacks and excessive requests.

Configure Rate Limiting Zone

# Edit main Nginx configuration

sudo nano /etc/nginx/nginx.conf

Add in http block:
limit_req_zone $binary_remote_addr zone=wp:10m rate=30r/m;
angelscript

🔍 Understanding the Configuration

  • $binary_remote_addr: Uses client IP address
  • zone=wp:10m: Creates 10MB zone named "wp"
  • rate=30r/m: Allows 30 requests per minute

Apply Rate Limiting to Specific Locations

# Create rate limiting configuration

sudo nano /etc/nginx/includes/rate_limiting_example.com.conf

Protect wp-login.php
location = /wp-login.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}

Protect xmlrpc.php
location = /xmlrpc.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}

Include in your server block
include /etc/nginx/includes/rate_limiting_example.com.conf;
angelscript

✅ Rate Limiting Parameters

  • burst=20: Allows burst of 20 requests before enforcing limit
  • nodelay: Immediately processes requests within burst limit
  • limit_req_status 444: Returns 444 status (connection closed) when limit exceeded

Browser Caching Headers

# Create browser caching configuration

sudo nano /etc/nginx/includes/browser_caching_security_headers.conf

expires 30d;
etag on;
if_modified_since exact;
add_header Pragma "public";
add_header Cache-Control "public, no-transform";
try_files $uri $uri/ /index.php?$args;
include /etc/nginx/includes/http_headers.conf;
access_log off;
xml

7. Troubleshooting & Maintenance

Checking Logs

# View Nginx error logs

cd /var/log/nginx
sudo tail -f error.log

View Nginx access logs
sudo tail -f access.log

View specific site access log
sudo tail -f example.com.access.log

View PHP-FPM pool error log
sudo tail -f /var/log/fpm-php.example.com.log

Less command for browsing logs
sudo less /var/log/nginx/error.log

Testing Configuration

# Test Nginx configuration syntax

sudo nginx -t

Reload Nginx after changes
sudo systemctl reload nginx

Restart Nginx (if reload doesn't work)
sudo systemctl restart nginx

Reload PHP-FPM
sudo systemctl reload php8.3-fpm

Restart PHP-FPM
sudo systemctl restart php8.3-fpm

Check Nginx status
sudo systemctl status nginx

Check PHP-FPM status
sudo systemctl status php8.3-fpm
xml

Common Issues and Solutions

Issue Possible Cause Solution
502 Bad Gateway PHP-FPM not running or wrong socket Check PHP-FPM status, verify socket path
403 Forbidden Permission issues or security rules Check file permissions and security directives
File upload fails Missing tmp directory or permissions Create tmp directory with correct ownership
Unable to send email User not in www-data group Add user to www-data group
Changes not visible Configuration not reloaded Reload or restart Nginx and PHP-FPM

Testing Connectivity

# Test HTTP connection

curl -I http://example.com

Test HTTPS connection
curl -I https://example.com

Test with verbose output
curl -v https://example.com

Test specific host header
curl -H "Host: example.com" http://SERVER_IP/

Verifying Pool Configuration

# Check pool is loaded

sudo grep "listen = /" /etc/php/8.3/fpm/pool.d/example.com.conf

List all running PHP-FPM processes
ps aux | grep php-fpm

Check PHP-FPM pools
sudo php-fpm8.3 -tt

View pool status (if enabled in pool config)
Add to pool config: pm.status_path = /status
Then access: https://example.com/status

Performance Monitoring

# Monitor server resources

htop

Check disk usage
df -h

Check memory usage
free -h

Monitor network connections
netstat -tulpn

Check open files
lsof | grep nginx
lsof | grep php-fpm
angelscript

💡 Pro Tips

  • Always test configuration with nginx -t before reloading
  • Keep backups of working configurations
  • Monitor logs regularly for errors and suspicious activity
  • Use version control (Git) for configuration files
  • Document any custom configurations or modifications

Maintenance Checklist

✅ Regular Maintenance Tasks

  • Daily: Monitor error logs for issues
  • Weekly: Check disk space and memory usage
  • Monthly: Review and rotate logs
  • Quarterly: Update security headers and SSL configuration
  • Bi-annually: Review and update PHP disabled functions list
  • Annually: Audit all user accounts and permissions

Emergency Recovery

# If Nginx fails to start, check configuration

sudo nginx -t

View detailed error messages
sudo journalctl -xe -u nginx

Restore from backup if needed
sudo cp /etc/nginx/sites-available/example.com.conf.backup
/etc/nginx/sites-available/example.com.conf

Restart services
sudo systemctl restart nginx
sudo systemctl restart php8.3-fpm
angelscript

8. Summary and Best Practices

Key Takeaways

Isolation

Each site operates independently with its own user, group, and resources

Security

Multiple layers of protection including permissions, headers, and rate limiting

Performance

Optimized caching, HTTP/3, and resource allocation per site

Best Practices Summary

🏆 Production Recommendations

  1. Always use separate PHP pools for each site or client
  2. Set hardened permissions (550/440) in production environments
  3. Enable HTTP/3 and QUIC for optimal performance
  4. Implement rate limiting on login and API endpoints
  5. Use strong SSL configuration targeting A+ SSL Labs rating
  6. Disable unnecessary PHP functions to reduce attack surface
  7. Monitor logs regularly and set up automated alerts
  8. Keep software updated (Nginx, PHP, WordPress, plugins)
  9. Automate SSL renewal with cron jobs
  10. Document all customizations and maintain backups

Configuration File Structure

/etc/nginx/

├── nginx.conf (main config)
├── sites-available/
│ └── example.com.conf (server block)
├── sites-enabled/
│ └── example.com.conf -> ../sites-available/example.com.conf
├── ssl/
│ ├── dhparam.pem
│ ├── ssl_all_sites.conf (global SSL config)
│ └── ssl_certs_example.com.conf (per-site certificates)
└── includes/
├── http_headers.conf
├── nginx_security_directives.conf
├── rate_limiting_example.com.conf
├── browser_caching_security_headers.conf
└── fastcgi_optimize.conf

/etc/php/8.3/fpm/
├── php-fpm.conf (main PHP-FPM config)
└── pool.d/
├── www.conf (default pool)
└── example.com.conf (site-specific pool)

/var/www/example.com/
├── public_html/ (web root)
├── tmp/ (temporary files)
└── logs/ (optional site-specific logs)
dts

Quick Reference Commands

Task Command
Test Nginx sudo nginx -t
Reload Nginx sudo systemctl reload nginx
Reload PHP-FPM sudo systemctl reload php8.3-fpm
View Nginx logs sudo tail -f /var/log/nginx/error.log
View PHP logs sudo tail -f /var/log/fpm-php.example.com.log
Renew SSL sudo certbot renew
Test SSL curl -I https://example.com
Set permissions sudo find . -type d -exec chmod 770 {} \;