Table of Contents
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
(256M)
(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:
Open your site's PHP pool configuration file:
Locate and modify the memory limit directive. Find the following line and uncomment it, then change the value from 32M to 256M:
Step 2: Configure WordPress Memory Limit
Navigate to your WordPress installation directory:
Open the WordPress configuration file:
Add the WordPress memory limit directive. Scroll to the section for custom directives (usually above the "/* That's all, stop editing! */" line) and add:
Step 3: Apply Changes
Restart the PHP-FPM service to apply the new configuration:
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:
Open wp-config.php:
Add the following directive to disable post revisions:
Restart PHP-FPM to apply changes:
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
in wp-config.php
(crontab)
Every 15 minutes
Step 1: Disable WordPress Built-in Cron
Open wp-config.php:
Add the following directive:
Restart PHP-FPM:
Step 2: Configure System Cron
Open the crontab editor:
Add the following cron job to run WordPress cron every 15 minutes:
- */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
(Fast)
(Slow)
Step 1: Configure OPcache for Development
Navigate to the PHP-FPM pool directory:
List available pool configurations:
Open your site's pool configuration:
Add OPcache configuration for development server:
Step 2: Configure OPcache for Production
For production servers, use this configuration instead:
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:
Step 4: Calculate Required max_accelerated_files
Count the total number of PHP files in your WordPress installation:
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
(Serve Cached)
Step 1: Configure NGINX HTTP Context
Navigate to the NGINX configuration directory:
Open the main NGINX configuration file:
Scroll to just above the "# Virtual Host Configs" comment and add the following directives:
- 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:
Create a new file for cache exclusion rules:
Add the following cache bypass rules:
Step 3: Configure Site-Specific Settings
Navigate to the sites-available directory:
Open your site's configuration file:
Add these directives inside the server block (before the location blocks):
Inside the PHP location block (location ~ \.php$), add:
Step 4: Configure Cache Purging (Optional)
Add a cache purge location block in your site configuration:
Step 5: Test and Apply Configuration
Test the NGINX configuration:
If the test passes, reload NGINX:
Step 6: Verify Cache Functionality
Check the cache status header:
- 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
Remove or comment out these directives:
2. Apply Changes
Restart PHP-FPM to clear OPcache:
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
(Serve Directly)
Generates Static File
Step 1: Create NGINX Exclusion Rules
Navigate to the NGINX includes directory:
Create a configuration file for WP Super Cache exclusions:
Add the following configuration:
- $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:
Open your site configuration:
Add the include directive inside your server block:
Step 3: Apply Configuration
Test NGINX configuration:
Reload NGINX:
Uninstalling WP Super Cache
1. Remove NGINX Configuration
Remove the include directive:
Uncomment the standard location block:
2. Apply Changes
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:
Create the nginx.conf file that W3TC will use:
Set proper ownership (replace 'username' with your actual username):
Set proper permissions:
Verify the file permissions:
Step 2: Secure the nginx.conf File
Navigate to NGINX includes directory:
Open the security directives file:
Add the following directive to deny access to nginx.conf:
Step 3: Create W3TC Cache Exclusion Rules
Add the following configuration:
Step 4: Configure Site-Specific Settings
Navigate to sites-available:
Open your site configuration:
Comment out the default location block:
Add the W3TC configuration includes:
Step 5: Install PHP Tidy Extension (Optional but Recommended)
W3TC can use the Tidy extension for HTML minification:
Step 6: Apply Configuration
Test NGINX configuration:
Reload services:
- 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
Restore the default location block:
Remove the W3TC includes:
3. Remove W3TC Cache Files
4. Remove nginx.conf File
5. Apply Changes
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
(Return from Memory)
Step 1: Install Redis Server and PHP Extension
Update package list:
Install Redis server and PHP Redis extension:
Step 2: Verify Redis Installation
Check Redis service status:
Check the Redis log file for any errors:
Step 3: Fix Memory Overcommit Warning
Navigate to sysctl configuration directory:
Create a Redis-specific configuration file:
Add the following directive:
Reboot the server to apply changes:
After reboot, verify the warning is gone:
Step 4: Configure Redis Memory Limits
Navigate to Redis configuration directory:
List Redis configuration files:
Open the Redis configuration file:
Find and set the maximum memory limit (search for "maxmemory"):
Set the eviction policy (search for "maxmemory-policy"):
- 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:
Reboot to ensure all changes persist:
Step 6: Configure WordPress for Redis
Navigate to your WordPress directory:
Open wp-config.php:
Add the Redis cache key salt (replace example.com with your domain):
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:
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:
View real-time Redis commands:
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
Master Process
(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:
Calculate Average Memory (replace POOL_USER with actual username):
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:
List available pools:
Open your site's pool configuration:
Add or modify the following directives:
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:
Step 4: Monitor for max_children Warnings
Check if your site is hitting the max_children limit:
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:
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:
Then configure NGINX to serve the status page (restrict access appropriately):
Access the status page:
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
(Real IP: 203.0.113.1)
(Proxy IP)
(Restores Real IP)
(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:
- https://www.cloudflare.com/ips-v4 (IPv4 addresses)
- https://www.cloudflare.com/ips-v6 (IPv6 addresses)
Step 2: Create Cloudflare IP Configuration File
Navigate to NGINX includes directory:
Create a new configuration file:
Add the Cloudflare IP ranges:
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:
Open your site configuration:
Add the include directive inside your server block (near the top, after the server_name directive):
Step 4: Test and Apply Configuration
Test NGINX configuration for syntax errors:
If the test passes, reload NGINX:
Step 5: Verify Configuration
Check that real IP addresses are being logged correctly:
You should now see actual visitor IP addresses instead of Cloudflare IPs in your logs.
Security Considerations
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:
Add a comment as a reminder:
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
Causes: PHP-FPM crashed, socket permission issues, max_children reached
Solutions: Check PHP-FPM status, increase max_children, check error logs
Causes: Object cache not configured, too many plugins, database bloat
Solutions: Install Redis, disable unnecessary plugins, optimize database
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
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.