NGINX WordPress Optimization Guide

Comprehensive Server Configuration and Performance Tuning

1. Memory Limit Configuration

When running a large and complex WordPress site with many plugins, it's crucial to increase the amount of memory that WordPress can use on the server to avoid memory errors and performance issues. This involves setting both the PHP memory limit and the WordPress memory limit.

Memory Configuration Flow

PHP Pool Config
PHP Memory Limit
(256M)
wp-config.php
WordPress Memory
(256M)

Understanding Memory Limits

  • PHP Memory Limit: Applies globally to all PHP scripts running on the server, but can be set on a per-site basis in our configuration
  • WordPress Memory Limit: Specifically controls the memory allocated to WordPress scripts and plugins

Step 1: Configure PHP Memory Limit

Navigate to your PHP pool configuration directory:

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

Open your site's PHP pool configuration file:

sudo nano example.com.conf

Locate and modify the memory limit directive. Find the following line and uncomment it, then change the value from 32M to 256M:

;php_admin_value[memory_limit] = 32M php_admin_value[memory_limit] = 256M
Note: The semicolon (;) at the beginning of a line comments it out. Removing it activates the directive.

Step 2: Configure WordPress Memory Limit

Navigate to your WordPress installation directory:

cd /var/www/example.com/

Open the WordPress configuration file:

sudo nano public_html/wp-config.php

Add the WordPress memory limit directive. Scroll to the section for custom directives (usually above the "/* That's all, stop editing! */" line) and add:

/** MEMORY LIMIT */ define('WP_MEMORY_LIMIT', '256M');

Step 3: Apply Changes

Restart the PHP-FPM service to apply the new configuration:

sudo systemctl restart php8.3-fpm
Success! Your WordPress site now has increased memory allocation, which will help prevent memory-related errors and improve performance.

Memory Configuration Comparison

Configuration Level File Location Directive Default Value Recommended Value
PHP Level /etc/php/8.3/fpm/pool.d/example.com.conf php_admin_value[memory_limit] 32M 256M
WordPress Level /var/www/example.com/public_html/wp-config.php WP_MEMORY_LIMIT 40M 256M

2. Post Revisions Management

WordPress automatically saves revisions of your posts and pages. While useful for content recovery, excessive revisions can bloat your database and impact performance. You can disable or limit post revisions through wp-config.php.

Disable Post Revisions

Navigate to your WordPress directory:

cd /var/www/example.com/

Open wp-config.php:

sudo nano public_html/wp-config.php

Add the following directive to disable post revisions:

define('WP_POST_REVISIONS', false);
Alternative: Instead of disabling completely, you can limit the number of revisions by using a number instead of 'false', e.g., define('WP_POST_REVISIONS', 3);

Restart PHP-FPM to apply changes:

sudo systemctl restart php8.3-fpm

Post Revision Options

Setting Code Effect
Disable Completely define('WP_POST_REVISIONS', false); No revisions saved
Limit Number define('WP_POST_REVISIONS', 3); Keep only 3 revisions
Default (No definition) Unlimited revisions

3. WP-Cron Configuration

WordPress uses WP-Cron to schedule tasks like publishing scheduled posts, checking for updates, and sending email notifications. By default, WP-Cron runs every time someone visits your site, which can impact performance. It's better to disable WP-Cron and use the system's cron daemon instead.

WP-Cron Optimization Process

Disable WP-Cron
in wp-config.php
Configure System Cron
(crontab)
Scheduled Execution
Every 15 minutes

Step 1: Disable WordPress Built-in Cron

Open wp-config.php:

sudo nano /var/www/example.com/public_html/wp-config.php

Add the following directive:

define('DISABLE_WP_CRON', true);

Restart PHP-FPM:

sudo systemctl restart php8.3-fpm

Step 2: Configure System Cron

Open the crontab editor:

crontab -e

Add the following cron job to run WordPress cron every 15 minutes:

*/15 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
Cron Job Explanation:
  • */15 * * * * - Run every 15 minutes
  • wget -q -O - - Quietly fetch the URL and output to stdout
  • >/dev/null 2>&1 - Discard all output

Alternative Cron Schedules

Frequency Cron Expression Use Case
Every 5 minutes */5 * * * * High-traffic sites with frequent updates
Every 15 minutes */15 * * * * Standard recommendation for most sites
Every 30 minutes */30 * * * * Low-traffic sites with infrequent updates
Every hour 0 * * * * Static sites with minimal scheduled tasks

4. OPcache Optimization

OPcache is a caching engine built into PHP that improves performance by storing precompiled script bytecode in shared memory. This eliminates the need for PHP to load and parse scripts on each request, significantly improving response times.

OPcache Performance Flow

PHP Request
Check OPcache
Cached Bytecode
(Fast)


Cache Miss
Compile PHP
(Slow)
Store in Cache

Step 1: Configure OPcache for Development

Navigate to the PHP-FPM pool directory:

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

List available pool configurations:

ls

Open your site's pool configuration:

sudo nano example.com.conf

Add OPcache configuration for development server:

; OPCACHE CONFIGURATION - DEVELOPMENT SERVER - Jul 2025 ; Directive php_admin_flag[opcache.enabled] leave commented - enabled by default ; php_admin_flag[opcache.enabled] = 1 php_admin_value[opcache.memory_consumption] = 256 php_admin_value[opcache.interned_strings_buffer] = 32 php_admin_value[opcache.max_accelerated_files] = 20000 php_admin_flag[opcache.validate_timestamps] = 1 php_admin_value[opcache.revalidate_freq] = 2 php_admin_flag[opcache.validate_permission] = 1

Step 2: Configure OPcache for Production

For production servers, use this configuration instead:

; OPCACHE CONFIGURATION - PRODUCTION SERVER - Jul 2025 ; Directive php_admin_flag[opcache.enabled] leave commented - enabled by default ;php_admin_flag[opcache.enabled] = 1 ;php_admin_value[opcache.memory_consumption] = 256 ;php_admin_value[opcache.interned_strings_buffer] = 32 ;php_admin_value[opcache.max_accelerated_files] = 20000 ;php_admin_value[opcache.validate_timestamps] = 0 ;php_admin_flag[opcache.validate_permission] = 1
Important: For production, set opcache.validate_timestamps = 0 for maximum performance. This means you'll need to manually clear OPcache after code changes.

Step 3: Apply Configuration

Reload PHP-FPM to apply changes:

sudo systemctl reload php8.3-fpm

Step 4: Calculate Required max_accelerated_files

Count the total number of PHP files in your WordPress installation:

cd /var/www/
sudo find . -type f -print | grep php | wc -l
Rule of Thumb: Set max_accelerated_files to approximately 2x the number of PHP files in your installation, rounded up to the nearest prime number for optimal hash table performance.

OPcache Configuration Reference

Directive Development Production Description
opcache.memory_consumption 256 MB 256 MB Shared memory size for cached scripts
opcache.interned_strings_buffer 32 MB 32 MB Memory for interned strings
opcache.max_accelerated_files 20000 20000 Maximum number of cached files
opcache.validate_timestamps 1 (On) 0 (Off) Check for file modifications
opcache.revalidate_freq 2 seconds N/A How often to check timestamps
opcache.validate_permission 1 (On) 1 (On) Validate file permissions

For detailed information on OPcache configuration options, visit: PHP OPcache Documentation

5. FastCGI Caching

FastCGI caching is a powerful NGINX feature that caches dynamic PHP content, dramatically reducing server load and improving response times. Unlike WordPress caching plugins, FastCGI caching operates at the NGINX level, making it extremely fast and efficient.

FastCGI Cache Architecture

User Request
NGINX
Cache Check


Cache HIT
(Serve Cached)
OR
Cache MISS
PHP-FPM
WordPress
Store in Cache

Step 1: Configure NGINX HTTP Context

Navigate to the NGINX configuration directory:

cd /etc/nginx

Open the main NGINX configuration file:

sudo nano nginx.conf

Scroll to just above the "# Virtual Host Configs" comment and add the following directives:

### FASTCGI CACHING # fastcgi_cache_path directive - PATH & NAME must be unique for each site # Add a new fastcgi_cache_path for each site and give a new keys_zone name fastcgi_cache_path /var/run/SITE levels=1:2 keys_zone=NAME:100m inactive=60m; # applied to all sites fastcgi_cache_key "$scheme$request_method$host$request_uri"; fastcgi_cache_use_stale error timeout invalid_header http_500; fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
Configuration Explained:
  • fastcgi_cache_path: Defines cache storage location and zone name
  • levels=1:2: Creates a two-level directory hierarchy for cache files
  • keys_zone=NAME:100m: Allocates 100MB for cache keys
  • inactive=60m: Removes cached items not accessed for 60 minutes
  • fastcgi_cache_key: Defines how cache keys are generated
  • fastcgi_cache_use_stale: Serves stale content during backend errors

Step 2: Create Cache Exclusion Rules

Navigate to the NGINX includes directory:

cd /etc/nginx/includes/

Create a new file for cache exclusion rules:

sudo nano fastcgi_cache_excludes.conf

Add the following cache bypass rules:

# NGINX SKIP CACHE INCLUDE FILE set $skip_cache 0; # POST requests and urls with a query string should always go to PHP if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # Don't cache uris containing the following segments if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { set $skip_cache 1; } # Don't use the cache for logged in users or recent commenters if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { set $skip_cache 1; }

Step 3: Configure Site-Specific Settings

Navigate to the sites-available directory:

cd /etc/nginx/sites-available

Open your site's configuration file:

sudo nano example.com.conf

Add these directives inside the server block (before the location blocks):

include /etc/nginx/includes/fastcgi_cache_excludes.conf; add_header X-FastCGI-Cache $upstream_cache_status;

Inside the PHP location block (location ~ \.php$), add:

fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache NAME; fastcgi_cache_valid 60m;

Step 4: Configure Cache Purging (Optional)

Add a cache purge location block in your site configuration:

location ~ /purge(/.*) { fastcgi_cache_purge NAME "$scheme$request_method$host$1"; }
Note: Replace "NAME" with your actual cache zone name defined in nginx.conf. You can use any path for purging, such as "/purge/", "/clear-cache/", etc.

Step 5: Test and Apply Configuration

Test the NGINX configuration:

sudo nginx -t

If the test passes, reload NGINX:

sudo systemctl reload nginx

Step 6: Verify Cache Functionality

Check the cache status header:

curl -I https://example.com
curl -I https://www.example.com
Cache Status Headers:
  • X-FastCGI-Cache: MISS - First request, not cached yet
  • X-FastCGI-Cache: HIT - Served from cache
  • X-FastCGI-Cache: BYPASS - Intentionally bypassed (logged in, POST request, etc.)
  • X-FastCGI-Cache: EXPIRED - Cache expired, regenerating

Removing FastCGI Caching

If you need to remove FastCGI caching:

1. Edit Site Configuration

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

Remove or comment out these directives:

# Remove this line: include /etc/nginx/includes/fastcgi_cache_excludes.conf; # Remove this line: add_header X-FastCGI-Cache $upstream_cache_status; # Comment out these lines in the PHP location block: #fastcgi_cache_bypass $skip_cache; #fastcgi_no_cache $skip_cache; #fastcgi_cache NAME; #fastcgi_cache_valid 60m; # Remove the purge location block: #location ~ /purge(/.*) { # fastcgi_cache_purge NAME "$scheme$request_method$host$1"; #}

2. Apply Changes

sudo nginx -t
sudo systemctl reload nginx

Restart PHP-FPM to clear OPcache:

sudo systemctl restart php8.3-fpm
Note: The fastcgi_cache directives in nginx.conf can remain as they only activate when called from a server block.

6. WP Super Cache Integration

WP Super Cache is a popular WordPress caching plugin that generates static HTML files from your dynamic WordPress site. When integrated with NGINX, it provides excellent performance by serving these static files directly without executing PHP.

WP Super Cache Flow

User Request
NGINX
Check Static HTML


Static File Exists
(Serve Directly)
OR
No Static File
PHP-FPM
WP Super Cache
Generates Static File

Step 1: Create NGINX Exclusion Rules

Navigate to the NGINX includes directory:

cd /etc/nginx/includes/

Create a configuration file for WP Super Cache exclusions:

sudo nano wp_super_cache_excludes.conf

Add the following configuration:

# WP Super Cache NGINX Cache Exclusions Rules. set $cache_uri $request_uri; # POST requests and urls with a query string should always go to PHP if ($request_method = POST) { set $cache_uri 'null cache'; } if ($query_string != "") { set $cache_uri 'null cache'; } # Don't cache uris containing the following segments if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") { set $cache_uri 'null cache'; } # Don't use the cache for logged in users or recent commenters if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $cache_uri 'null cache'; } # Use cached or actual file if they exists, otherwise pass request to WordPress location / { try_files /wp-content/cache/supercache/$http_host/$cache_uri/index-https.html $uri $uri/ /index.php?$args ; }
Configuration Explained:
  • $cache_uri: Variable to track whether caching should be used
  • try_files: Attempts to serve static cache file first, then falls back to dynamic content
  • index-https.html: WP Super Cache generates separate files for HTTPS requests

Step 2: Configure Site-Specific Settings

Navigate to sites-available directory:

cd /etc/nginx/sites-available/

Open your site configuration:

sudo nano example.com.conf

Add the include directive inside your server block:

include /etc/nginx/includes/wp_super_cache_excludes.conf;

Step 3: Apply Configuration

Test NGINX configuration:

sudo nginx -t

Reload NGINX:

sudo systemctl reload nginx

Uninstalling WP Super Cache

1. Remove NGINX Configuration

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

Remove the include directive:

# REMOVE: include /etc/nginx/includes/wp_super_cache_excludes.conf;

Uncomment the standard location block:

location / { try_files $uri $uri/ /index.php$is_args$args; }

2. Apply Changes

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm

3. Remove Plugin from WordPress

Deactivate and delete WP Super Cache from the WordPress admin panel.

WP Super Cache vs FastCGI Cache

Feature WP Super Cache FastCGI Cache
Cache Location WordPress directory NGINX memory/disk
Management WordPress admin Server configuration
Performance Very Fast Extremely Fast
Flexibility High (GUI controls) Medium (config files)
Resource Usage Low Very Low

7. W3 Total Cache Integration

W3 Total Cache (W3TC) is one of the most comprehensive caching plugins for WordPress, offering page caching, object caching, database caching, and CDN integration. When properly configured with NGINX, it provides exceptional performance improvements.

Step 1: Prepare WordPress Directory

Navigate to your WordPress installation:

cd /var/www/example.com/public_html/

Create the nginx.conf file that W3TC will use:

sudo touch nginx.conf

Set proper ownership (replace 'username' with your actual username):

sudo chown username:username nginx.conf

Set proper permissions:

sudo chmod 660 nginx.conf

Verify the file permissions:

sudo ls -l nginx.conf

Step 2: Secure the nginx.conf File

Navigate to NGINX includes directory:

cd /etc/nginx/includes/

Open the security directives file:

sudo nano nginx_security_directives.conf

Add the following directive to deny access to nginx.conf:

location = /nginx.conf { deny all; }

Step 3: Create W3TC Cache Exclusion Rules

cd /etc/nginx/includes/
sudo nano w3tc_cache_excludes.conf

Add the following configuration:

# --------------------- # W3 TOTAL CACHE EXCLUDES FILE # --------------------- set $cache_uri $request_uri; # POST requests and urls with a query string should always go to PHP if ($request_method = POST) { set $cache_uri 'null cache'; } if ($query_string != "") { set $cache_uri 'null cache'; } # Don't cache uris containing the following segments if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") { set $cache_uri 'null cache'; } # Don't use the cache for logged in users or recent commenters if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $cache_uri 'null cache'; } # Use cached or actual file if file exists, otherwise pass request to WordPress location / { try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?$args; }

Step 4: Configure Site-Specific Settings

Navigate to sites-available:

cd /etc/nginx/sites-available/

Open your site configuration:

sudo nano example.com.conf

Comment out the default location block:

#location / { # try_files $uri $uri/ /index.php$is_args$args; #}

Add the W3TC configuration includes:

include /etc/nginx/includes/w3tc_cache_excludes.conf; include /var/www/example.com/public_html/nginx.conf;

Step 5: Install PHP Tidy Extension (Optional but Recommended)

W3TC can use the Tidy extension for HTML minification:

sudo apt update
sudo apt install php8.3-tidy

Step 6: Apply Configuration

Test NGINX configuration:

sudo nginx -t

Reload services:

sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
W3TC Configuration in WordPress:
  • Navigate to Performance → General Settings in WordPress admin
  • Enable Page Caching and select "Disk: Enhanced" method
  • Enable Browser Caching
  • Configure Object Caching (Redis recommended - see next section)
  • Enable Minification for HTML, JS, and CSS

Uninstalling W3 Total Cache

1. Deactivate and Remove Plugin

First, deactivate W3 Total Cache from the WordPress admin panel.

2. Remove NGINX Configuration

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

Restore the default location block:

location / { try_files $uri $uri/ /index.php$is_args$args; }

Remove the W3TC includes:

# REMOVE THESE LINES: include /etc/nginx/includes/w3tc_cache_excludes.conf; include /var/www/example.com/public_html/nginx.conf;

3. Remove W3TC Cache Files

cd /var/www/example.com/public_html/wp-content/
sudo rm -rf cache/ w3tc-config/

4. Remove nginx.conf File

cd /var/www/example.com/public_html/
sudo rm nginx.conf

5. Apply Changes

sudo systemctl reload php8.3-fpm
sudo systemctl reload nginx

W3TC Cache Directory Structure

Cache Type Location Purpose
Page Cache /wp-content/w3tc/pgcache/ Cached HTML pages
Minified Files /wp-content/cache/minify/ Minified CSS and JavaScript
Configuration /wp-content/w3tc-config/ W3TC settings and configuration
Database Cache /wp-content/w3tc/dbcache/ Cached database queries

8. Redis Object Caching

Redis is an in-memory data structure store that serves as a high-performance object cache for WordPress. By caching database query results and object data in RAM, Redis dramatically reduces database load and improves response times, especially for dynamic content that cannot be served from page cache.

Redis Cache Architecture

WordPress Request
Check Redis Cache


Cache HIT
(Return from Memory)
OR
Cache MISS
Query Database
Store in Redis
Return Result

Step 1: Install Redis Server and PHP Extension

Update package list:

sudo apt update

Install Redis server and PHP Redis extension:

sudo apt install redis-server php8.3-redis

Step 2: Verify Redis Installation

Check Redis service status:

sudo systemctl status redis-server

Check the Redis log file for any errors:

sudo cat /var/log/redis/redis-server.log
Common Warning: You may see "WARNING overcommit_memory is set to 0! Background save may fail under low memory condition." This needs to be fixed in the next step.

Step 3: Fix Memory Overcommit Warning

Navigate to sysctl configuration directory:

cd /etc/sysctl.d/

Create a Redis-specific configuration file:

sudo nano 11-redis.conf

Add the following directive:

vm.overcommit_memory = 1

Reboot the server to apply changes:

sudo reboot

After reboot, verify the warning is gone:

sudo systemctl status redis-server
sudo cat /var/log/redis/redis-server.log

Step 4: Configure Redis Memory Limits

Navigate to Redis configuration directory:

cd /etc/

List Redis configuration files:

sudo ls redis/

Open the Redis configuration file:

sudo nano redis/redis.conf

Find and set the maximum memory limit (search for "maxmemory"):

maxmemory 256mb

Set the eviction policy (search for "maxmemory-policy"):

maxmemory-policy allkeys-lru
Eviction Policies Explained:
  • allkeys-lru: Removes least recently used keys when memory limit is reached (recommended for WordPress)
  • volatile-lru: Only removes LRU keys with an expiration set
  • allkeys-lfu: Removes least frequently used keys
  • noeviction: Returns errors when memory limit is reached

Step 5: Restart Redis

Restart Redis server:

sudo systemctl restart redis-server

Reboot to ensure all changes persist:

sudo reboot

Step 6: Configure WordPress for Redis

Navigate to your WordPress directory:

cd /var/www/example.com/public_html/

Open wp-config.php:

sudo nano wp-config.php

Add the Redis cache key salt (replace example.com with your domain):

define( 'WP_CACHE_KEY_SALT', 'example.com' );
Important: The cache key salt ensures that different WordPress installations using the same Redis instance don't have conflicting cache keys.

Step 7: WooCommerce Specific Configuration

If you're running WooCommerce, you need to prevent Redis from caching session data to avoid cart and checkout issues.

Add to wp-config.php:

/** PREVENT REDIS CACHING WOO SESSION DATA */ define('WP_REDIS_IGNORED_GROUPS', 'wc_session');
WooCommerce Cache Exclusions:

Ensure your caching configuration excludes these WooCommerce pages and cookies:

Pages to Exclude:

  • /cart/
  • /my-account/
  • /checkout/

Cookies to Check:

  • woocommerce_cart_hash
  • woocommerce_items_in_cart
  • wp_woocommerce_session_
  • woocommerce_recently_viewed
  • store_notice[notice id]
  • _wc_session_

Step 8: Install Redis Object Cache Plugin

Install and activate the "Redis Object Cache" plugin from the WordPress plugin repository. After activation, go to Settings → Redis and click "Enable Object Cache".

Redis Configuration Reference

Configuration File Directive Recommended Value
Memory Limit /etc/redis/redis.conf maxmemory 256mb
Eviction Policy /etc/redis/redis.conf maxmemory-policy allkeys-lru
Memory Overcommit /etc/sysctl.d/11-redis.conf vm.overcommit_memory 1
Cache Key Salt wp-config.php WP_CACHE_KEY_SALT your-domain.com
WooCommerce Exclusion wp-config.php WP_REDIS_IGNORED_GROUPS wc_session

Monitoring Redis

You can monitor Redis performance using the Redis CLI:

redis-cli info memory
redis-cli info stats

View real-time Redis commands:

redis-cli monitor
For detailed WooCommerce caching configuration, visit:
WooCommerce Caching Documentation

9. PHP-FPM Optimization

PHP-FPM (FastCGI Process Manager) is the PHP processor that handles all PHP requests from NGINX. Proper PHP-FPM configuration is critical for optimal WordPress performance, as it directly affects how many concurrent users your site can handle and how efficiently server resources are utilized.

PHP-FPM Process Management

NGINX Request
PHP-FPM
Master Process
Worker Pool


Worker 1
Worker 2
Worker 3
Worker N
(max_children)

Understanding Process Manager Modes

PHP-FPM offers different process manager modes, each with distinct characteristics:

Mode Behavior Best For
static Fixed number of workers always running High-traffic production sites with consistent load
dynamic Workers spawn and die based on demand Sites with variable traffic patterns
ondemand Workers spawn only when needed, die when idle Low-traffic sites, development servers, VPS with limited RAM

Step 1: Calculate Average Process Memory

Before configuring PHP-FPM, you need to determine how much memory each PHP process uses on average. This is crucial for calculating the optimal max_children value.

Display Pool Usernames:

grep -E '^\s*user\s*=' /etc/php/8.3/fpm/pool.d/*.conf | awk -F= '{print $2}' | xargs | tr ' ' '\n' | sort -u

Calculate Average Memory (replace POOL_USER with actual username):

ps -C php-fpm --user POOL_USER -o rss= | awk '{ sum += $1; count++ } END { if (count > 0) printf ("%d%s\n", sum/NR/1024,"M") }'
Example Calculation:

If average memory per process is 50MB and you have 2GB RAM available for PHP:

max_children = 2048MB ÷ 50MB = ~40 workers

Always leave some headroom for system processes and other services.

Step 2: Configure OnDemand Mode

For most sites, especially on VPS or development servers, the "ondemand" mode is recommended as it conserves memory by only spawning workers when needed.

Navigate to PHP-FPM pool directory:

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

List available pools:

ls

Open your site's pool configuration:

sudo nano example.com.conf

Add or modify the following directives:

pm = ondemand pm.max_children = 40 pm.process_idle_timeout = 10s pm.max_requests = 500

Configuration Directives Explained

Directive Purpose Recommended Value
pm Process manager type ondemand (for low-traffic) or dynamic (for high-traffic)
pm.max_children Maximum number of worker processes Based on available memory calculation
pm.process_idle_timeout Seconds before idle process terminates (ondemand only) 10s - 30s
pm.max_requests Number of requests before worker respawns (prevents memory leaks) 500 - 1000

Step 3: Apply Configuration

Reload PHP-FPM to apply changes:

sudo systemctl reload php8.3-fpm

Step 4: Monitor for max_children Warnings

Check if your site is hitting the max_children limit:

ls /var/log/
sudo grep max_children /var/log/php8.3-fpm.log
Warning Message:

If you see "WARNING: [pool www] server reached max_children setting (25), consider raising it", you need to increase pm.max_children value.

Advanced Configuration: Dynamic Mode

For high-traffic production sites, consider using dynamic mode:

pm = dynamic pm.max_children = 50 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 15 pm.max_requests = 500

Dynamic Mode Directives:

Directive Purpose Calculation
pm.start_servers Workers spawned at startup min_spare + (max_spare - min_spare) / 2
pm.min_spare_servers Minimum idle workers ~10% of max_children
pm.max_spare_servers Maximum idle workers ~30% of max_children

Monitoring PHP-FPM Performance

Enable status page in your pool configuration:

pm.status_path = /status

Then configure NGINX to serve the status page (restrict access appropriately):

location ~ ^/(status|ping)$ { access_log off; allow 127.0.0.1; deny all; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock; }

Access the status page:

curl http://localhost/status?full

PHP-FPM Optimization Checklist

  • ✓ Calculate average memory per process
  • ✓ Set appropriate pm.max_children based on available RAM
  • ✓ Choose correct process manager mode (ondemand vs dynamic)
  • ✓ Set pm.max_requests to prevent memory leaks
  • ✓ Monitor logs for max_children warnings
  • ✓ Enable status page for ongoing monitoring
  • ✓ Adjust values based on actual traffic patterns

10. Cloudflare Integration

When your WordPress site is behind Cloudflare's reverse proxy, NGINX needs special configuration to correctly log and process real visitor IP addresses. Without this configuration, all requests will appear to come from Cloudflare's IP addresses rather than actual visitors, breaking analytics, security features, and geolocation functionality.

Cloudflare Request Flow

Visitor
(Real IP: 203.0.113.1)
Cloudflare
(Proxy IP)
NGINX
(Restores Real IP)
WordPress
(Sees: 203.0.113.1)

Understanding the Problem

Cloudflare acts as a reverse proxy, meaning all traffic to your site comes through Cloudflare's servers. The actual visitor's IP address is passed in the CF-Connecting-IP header. NGINX needs to be configured to trust Cloudflare's IP ranges and extract the real IP from this header.

Step 1: Obtain Current Cloudflare IP Ranges

Cloudflare provides their current IP ranges at these URLs:

Important: Cloudflare's IP ranges can change over time. Check these URLs periodically and update your configuration as needed. Consider setting up automated checks or subscribing to Cloudflare's change notifications.

Step 2: Create Cloudflare IP Configuration File

Navigate to NGINX includes directory:

cd /etc/nginx/includes

Create a new configuration file:

sudo nano cloudflare_ip_list.conf

Add the Cloudflare IP ranges:

# Last updated FEB 2026 # IPv4 Ranges set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; # IPv6 Ranges set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29; set_real_ip_from 2c0f:f248::/32; # Extract real IP from Cloudflare header real_ip_header CF-Connecting-IP;

Configuration Directives Explained

Directive Purpose Example
set_real_ip_from Defines trusted proxy IP ranges set_real_ip_from 173.245.48.0/20;
real_ip_header Specifies which header contains real IP real_ip_header CF-Connecting-IP;

Step 3: Include Configuration in Site Config

Navigate to sites-available directory:

cd /etc/nginx/sites-available/

Open your site configuration:

sudo nano example.com.conf

Add the include directive inside your server block (near the top, after the server_name directive):

include /etc/nginx/includes/cloudflare_ip_list.conf;

Step 4: Test and Apply Configuration

Test NGINX configuration for syntax errors:

sudo nginx -t

If the test passes, reload NGINX:

sudo systemctl reload nginx

Step 5: Verify Configuration

Check that real IP addresses are being logged correctly:

sudo tail -f /var/log/nginx/access.log

You should now see actual visitor IP addresses instead of Cloudflare IPs in your logs.

Security Considerations

Security Warning:

If you use Cloudflare's real IP restoration, make sure your server is ONLY accessible through Cloudflare. If attackers can bypass Cloudflare and connect directly to your server, they can spoof the CF-Connecting-IP header.

Protect Your Origin Server:

  • Use Cloudflare's Authenticated Origin Pulls (TLS Client Authentication)
  • Configure firewall rules to only allow Cloudflare IP ranges
  • Change your server's IP address after enabling Cloudflare
  • Disable direct IP access to your site

Cloudflare + NGINX Benefits

Feature Without Real IP Config With Real IP Config
Analytics All traffic from Cloudflare IPs Accurate visitor locations and IPs
Security Plugins Can't block malicious IPs Can identify and block threats
Geolocation All visitors appear from USA Correct geographic data
Rate Limiting Affects all visitors together Per-visitor rate limits work correctly
Access Logs Cloudflare IPs logged Real visitor IPs logged

Maintenance and Updates

Create a reminder to check for Cloudflare IP range updates quarterly:

crontab -e

Add a comment as a reminder:

# Check Cloudflare IP ranges quarterly: https://www.cloudflare.com/ips-v4 and /ips-v6

Performance Optimization Summary

Implementing all the optimizations covered in this guide will significantly improve your WordPress site's performance. Here's a comprehensive summary of what we've accomplished:

Complete Optimization Stack

Layer Technology Primary Benefit Performance Impact
CDN / Proxy Cloudflare Global content delivery, DDoS protection High
Web Server NGINX Efficient request handling, static file serving High
Page Cache FastCGI / WP Super Cache / W3TC Serve static HTML, bypass PHP Very High
Bytecode Cache OPcache Precompiled PHP scripts High
Object Cache Redis Database query caching Medium-High
PHP Processing PHP-FPM Efficient PHP execution Medium
Application WordPress Optimized configuration Medium

Performance Tuning Recommendations by Site Type

Small Blog / Personal Site

  • Memory Limit: 128M - 256M
  • Page Cache: WP Super Cache
  • Object Cache: Not required (optional)
  • PHP-FPM: ondemand mode, max_children: 10-20
  • OPcache: Development settings

Business Site / E-commerce

  • Memory Limit: 256M - 512M
  • Page Cache: FastCGI Cache or W3 Total Cache
  • Object Cache: Redis (required)
  • PHP-FPM: dynamic mode, max_children: 30-50
  • OPcache: Production settings

High-Traffic Site

  • Memory Limit: 512M - 1024M
  • Page Cache: FastCGI Cache (multiple servers)
  • Object Cache: Redis (dedicated server)
  • PHP-FPM: static mode, max_children: 50+
  • OPcache: Production settings, high memory allocation

Monitoring and Maintenance

Regular monitoring is essential to maintain optimal performance:

Weekly Tasks:

  • Check PHP-FPM logs for max_children warnings
  • Monitor Redis memory usage
  • Review NGINX access and error logs
  • Check cache hit ratios

Monthly Tasks:

  • Clear expired OPcache entries
  • Analyze slow query logs
  • Review and optimize database tables
  • Update WordPress, themes, and plugins

Quarterly Tasks:

  • Update Cloudflare IP ranges
  • Review and adjust PHP-FPM settings based on traffic patterns
  • Audit and remove unnecessary plugins
  • Performance benchmark tests

Key Performance Metrics to Monitor

Metric Target Value Tool
Time to First Byte (TTFB) < 200ms Chrome DevTools, WebPageTest
First Contentful Paint (FCP) < 1.8s Google PageSpeed Insights
Largest Contentful Paint (LCP) < 2.5s Google PageSpeed Insights
Cache Hit Rate > 90% NGINX logs, Redis stats
Server Response Time < 600ms GTmetrix, Pingdom

Troubleshooting Common Issues

Issue: 502 Bad Gateway

Causes: PHP-FPM crashed, socket permission issues, max_children reached

Solutions: Check PHP-FPM status, increase max_children, check error logs

Issue: Slow Admin Dashboard

Causes: Object cache not configured, too many plugins, database bloat

Solutions: Install Redis, disable unnecessary plugins, optimize database

Issue: Cache Not Clearing

Causes: Plugin misconfiguration, permission issues, cron not running

Solutions: Check file permissions, verify cron job, manually purge cache

Final Recommendations

  • Start with basic optimizations (memory, OPcache) before implementing complex caching
  • Test each optimization individually to measure its impact
  • Choose ONE page caching solution (FastCGI, WP Super Cache, or W3TC) - don't mix them
  • Always maintain backups before making configuration changes
  • Document your configuration changes for future reference
  • Monitor performance metrics regularly and adjust as needed
  • Keep all software (NGINX, PHP, WordPress) updated to latest stable versions
Congratulations!

You now have a comprehensive understanding of WordPress performance optimization with NGINX. By implementing these configurations, your WordPress site should see significant improvements in speed, scalability, and user experience.