WordPress Performance Optimization

Complete Guide to Caching & Server Configuration

Understanding Dynamic Site Caching

Why Caching Matters

Caching a dynamic site is essential for improving performance and reducing server load. This is especially true for content management systems like WordPress. Page caching solutions such as FastCGI caching, WP Super Cache, and W3 Total Cache are commonly used to cache static HTML versions of dynamic pages, thereby speeding up load times for visitors.

Caching Architecture

User Request

Browser requests page

Page Cache

Static HTML delivery

Object Cache

Database queries cached

Fast Response

Optimized delivery

Caching Strategies

Page Caching

Stores static HTML versions of dynamically generated pages. This reduces server load and speeds up page delivery to visitors.

  • FastCGI Caching
  • WP Super Cache
  • W3 Total Cache

Object Caching

Caches database queries, objects, and other dynamic content, thereby further enhancing your site's performance.

  • Redis Object Cache
  • Memcached
  • APCu
Important Considerations:

Certain dynamic elements, such as user-specific content or shopping cart details, should be excluded from page caching to ensure accuracy and security. Page cache exclusions are essential to prevent caching of these dynamic elements.

Recommended Approach

It's generally not recommended to use FastCGI caching in conjunction with object caching due to potential conflicts and limitations. Instead, a more reliable approach involves pairing either WP Super Cache or W3 Total Cache with object caching.

Caching Solution Best For Object Cache Support Complexity
FastCGI Cache Static sites Separate configuration High
WP Super Cache Dynamic sites Compatible with Redis Medium
W3 Total Cache Dynamic sites Built-in support Low

1. Disabling Post Revisions

WordPress automatically saves multiple revisions of your posts and pages. While useful for tracking changes, excessive revisions can bloat your database and slow down your site. Disabling post revisions can improve database performance.

Reference: Cloudflare has deprecated auto-minify feature. See: Cloudflare Community

Implementation Steps

Navigate to WordPress directory
cd /var/www/example.com/
Edit wp-config.php
sudo nano public_html/wp-config.php

Add the following line to disable post revisions:

Configuration directive
define('WP_POST_REVISIONS', 'false');
Restart PHP-FPM service
sudo systemctl restart php8.3-fpm
Result: Post revisions are now disabled, reducing database overhead and improving performance.

2. Configuring Memory Limits

Properly configuring memory limits is crucial for WordPress performance. PHP and WordPress both have memory limits that should be optimized based on your site's requirements.

PHP-FPM Pool Configuration

Navigate to PHP-FPM pool directory
cd /etc/php/8.3/fpm/pool.d/
List pool configurations
ls
Edit pool configuration
sudo nano example.com.conf

Update the memory limit (comment out the 32M line and uncomment/set 256M):

Pool memory configuration
;php_admin_value[memory_limit] = 32M
php_admin_value[memory_limit] = 256M

WordPress Memory Configuration

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

Add the WordPress memory limit directive:

WordPress memory limit
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
Restart PHP-FPM
sudo systemctl restart php8.3-fpm
Note: 256M is recommended for most WordPress installations. Adjust based on your site's specific needs and available server resources.

3. Optimizing WP Cron

By default, WordPress triggers WP-Cron on every page load, which can impact performance. Using system cron instead provides better control and reliability.

Disable WordPress Built-in Cron

Edit wp-config.php
sudo nano /var/www/example.com/public_html/wp-config.php
Add disable directive
define('DISABLE_WP_CRON', true);
Restart PHP-FPM
sudo systemctl restart php8.3-fpm

Configure System Cron

Edit crontab
crontab -e

Add the following cron job to run every 15 minutes:

Cron job configuration
*/15 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Cron Job Execution Flow

Every 15 Minutes

System triggers

wget Command

Fetches wp-cron.php

WordPress

Executes scheduled tasks

Completion

Silent execution

Benefits:
  • Scheduled tasks run reliably regardless of site traffic
  • No performance impact on page loads
  • Better control over execution timing

4. Configuring OPcache

OPcache improves PHP performance by storing precompiled script bytecode in shared memory, eliminating the need for PHP to load and parse scripts on each request.

Calculate max_accelerated_files

Navigate to web root
cd /var/www/
Count PHP files
sudo find . -type f -print | grep php | wc -l
Reference: For detailed OPcache configuration options, see: PHP OPcache Documentation

Development Server Configuration

Navigate to pool directory
cd /etc/php/8.3/fpm/pool.d/
Edit pool configuration
sudo nano example.com.conf

Add the following OPcache configuration for development:

OPcache - Development Server (July 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

Production Server Configuration

OPcache - Production Server (July 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
Reload PHP-FPM
sudo systemctl reload php8.3-fpm
Directive Development Production Description
memory_consumption 256 MB 256 MB Memory allocated for OPcache
interned_strings_buffer 32 MB 32 MB Memory for interned strings
max_accelerated_files 20000 20000 Maximum number of cached files
validate_timestamps 1 (enabled) 0 (disabled) Check file timestamps for changes
revalidate_freq 2 seconds N/A Frequency of timestamp checks
Important: In production, set validate_timestamps = 0 for maximum performance. This means OPcache won't check for file changes, so you'll need to manually reload PHP-FPM after code updates.

5. FastCGI Caching

Recommendation for Dynamic Sites: FastCGI caching is not recommended for dynamic sites due to complications with cache exclusions. It's better suited for static sites. For dynamic WordPress sites, consider WP Super Cache or W3 Total Cache instead.

HTTP Context Configuration

Navigate to Nginx configuration
cd /etc/nginx
Edit main configuration
sudo nano nginx.conf

Add these directives just above the "Virtual Host Configs" comment:

FastCGI cache configuration
### FASTCGI CACHING
# fastcgi_cache_path directive - PATH & NAME must be unique for each site
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;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache NAME;
fastcgi_cache_valid 60m;

Cache Exclusions Configuration

Navigate to includes directory
cd /etc/nginx/includes/
Create exclusions file
sudo nano fastcgi_cache_excludes.conf
Exclusions configuration
# 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;
}

Server Block Configuration

Navigate to sites-available
cd /etc/nginx/sites-available
Edit site configuration
sudo nano example.com.conf

Add these lines to your server block:

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

Cache Purging

Add this location block to enable cache purging (use any name you prefer instead of /purge/):

Purge location block
location ~ /purge(/.*) {
fastcgi_cache_purge NAME "$scheme$request_method$host$1";
}

Test and Apply Configuration

Test Nginx configuration
sudo nginx -t
Reload Nginx
sudo systemctl reload nginx
Restart PHP-FPM
sudo systemctl restart php8.3-fpm

Verify Caching

Check HTTPS response
curl -I https://example.com
Check WWW response
curl -I https://www.example.com

Look for the X-FastCGI-Cache header in the response. Values can be:

  • MISS - Response not cached (first request)
  • HIT - Response served from cache
  • BYPASS - Cache bypassed due to exclusion rules

Removing FastCGI Caching

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

Remove or comment out the following:

Remove these directives
include /etc/nginx/includes/fastcgi_cache_excludes.conf;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache NAME;
fastcgi_cache_valid 60m;
add_header X-FastCGI-Cache $upstream_cache_status;
location ~ /purge(/.*) { ... }
Test and reload
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm

6. WP Super Cache Configuration

WP Super Cache is a popular WordPress caching plugin that generates static HTML files from your dynamic WordPress blog. This is one of the recommended solutions for dynamic WordPress sites.

Recommended for Dynamic Sites: WP Super Cache can be combined with object caching (Redis) to improve site speed and responsiveness while maintaining dynamic content accuracy.

Nginx Cache Exclusions

Navigate to includes directory
cd /etc/nginx/includes/
Create exclusions file
sudo nano wp_super_cache_excludes.conf
WP Super Cache exclusions 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 ;
}

Server Block Configuration

Navigate to sites-available
cd sites-available/
Edit site configuration
sudo nano example.com.conf

Add this include directive:

Include WP Super Cache exclusions
include /etc/nginx/includes/wp_super_cache_exclusions.conf;
Test and reload Nginx
sudo nginx -t
sudo systemctl reload nginx

Uninstalling WP Super Cache

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

Remove:

Remove include directive
include /etc/nginx/includes/wp_super_cache_exclusions.conf;

Uncomment:

Restore default location block
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
Reload services
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm

7. W3 Total Cache Configuration

W3 Total Cache (W3TC) is a comprehensive caching plugin that offers advanced caching options and built-in support for object caching, making it an excellent choice for managing both page caching and object caching in one solution.

Best Choice for Dynamic Sites: W3TC offers built-in support for Redis, thereby simplifying the setup process and enabling users to harness the benefits of Redis without the need for additional plugins or configurations.

Prepare nginx.conf File

Navigate to WordPress directory
cd /var/www/example.com/public_html/
Create nginx.conf file
sudo touch nginx.conf
Set ownership
sudo chown username:username nginx.conf
Set permissions
sudo chmod 660 nginx.conf
Verify permissions
sudo ls -l public_html/

Security Configuration

Navigate to includes directory
cd /etc/nginx/includes/
Edit security directives
sudo nano nginx_security_directives.conf

Add this line to deny access to nginx.conf:

Deny nginx.conf access
location = /nginx.conf { deny all; }

Cache Exclusions Configuration

Create W3TC exclusions file
cd /etc/nginx/includes/
sudo nano w3tc_cache_excludes.conf
W3TC cache exclusions
# ---------------------
# 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;
}

Server Block Configuration

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

Comment out the default location block:

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

Add these include directives:

Include W3TC configuration
include /etc/nginx/includes/w3tc_cache_excludes.conf;
include /var/www/example.com/public_html/nginx.conf;
Test and reload
sudo nginx -t
sudo systemctl reload nginx

Install PHP Tidy Extension

Update package list
sudo apt update
Install PHP Tidy
sudo apt install php8.3-tidy
Reload services
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm

Uninstalling W3TC

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

Uncomment default location:

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

Remove include directives:

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

Remove cache files:

Navigate to wp-content
cd /var/www/example.com/public_html/wp-content/
Remove cache directories
sudo rm -rf cache/ w3tc-config/
Remove nginx.conf
cd /var/www/example.com/public_html/
sudo rm nginx.conf
Reload services
sudo systemctl reload php8.3-fpm
sudo systemctl reload nginx

8. Redis Object Cache

Redis is an in-memory data structure store that can be used as a database, cache, and message broker. When integrated with WordPress, Redis caches database queries and objects, significantly improving site performance.

Perfect Combination: Redis object caching pairs excellently with WP Super Cache or W3 Total Cache to provide comprehensive caching coverage for dynamic WordPress sites.

Installation

Update package list
sudo apt update
Install Redis and PHP extension
sudo apt install redis-server php8.3-redis

Verify Installation

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

Fix Memory Overcommit Warning

If you see this warning in the log: WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.

Navigate to sysctl.d
cd /etc/sysctl.d/
Create Redis configuration
sudo nano 11-redis.conf
Set overcommit memory
vm.overcommit_memory = 1
Reboot system
sudo reboot

After reboot, verify the fix:

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

Configure Redis Memory Settings

List Redis configuration
cd /etc/
sudo ls redis/
Edit Redis configuration
sudo nano redis/redis.conf

Add or modify these directives:

Memory configuration
maxmemory 256mb
maxmemory-policy allkeys-lru
Restart Redis and reboot
sudo systemctl restart redis-server
sudo reboot
Setting Value Description
maxmemory 256mb Maximum memory Redis can use
maxmemory-policy allkeys-lru Eviction policy - removes least recently used keys
vm.overcommit_memory 1 Allows Redis to allocate more memory than physically available

WordPress Configuration

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

Add this configuration directive:

Redis cache key salt
define( 'WP_CACHE_KEY_SALT', 'example.com' );

WooCommerce Integration

WooCommerce Configuration Reference: See WooCommerce caching documentation

To prevent Redis from caching WooCommerce session data:

Prevent caching WooCommerce sessions
/** PREVENT REDIS CACHING WOO SESSION DATA */
define('WP_REDIS_IGNORED_GROUPS', 'wc_session');

WooCommerce Cache Exclusions

URLs to exclude from caching:

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

Cookies to exclude from caching:

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

9. PHP-FPM Process Manager Optimization

Calculate Memory Requirements

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 per process (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") }'

OnDemand Configuration

Navigate to pool directory
cd /etc/php/8.3/fpm/pool.d/
ls
Edit pool configuration
sudo nano example.com.conf
OnDemand process manager settings
pm = ondemand
pm.max_children = as calculated
pm.process_idle_timeout = 10s;
pm.max_requests = 500
Reload PHP-FPM
sudo systemctl reload php8.3-fpm

Monitor Max Children

List log directory
ls /var/log/
Check for max_children warnings
sudo grep max_children /var/log/php8.3-fpm.log
Warning to Watch For:

WARNING: [pool www] server reached max_children setting (25), consider raising it

If you see this warning, increase the pm.max_children value in your pool configuration.

Setting Description Recommended Value
pm Process manager type ondemand
pm.max_children Maximum child processes Based on memory calculation
pm.process_idle_timeout Time before idle process is killed 10s
pm.max_requests Requests before process restart 500

10. Cloudflare Integration

When using Cloudflare as a reverse proxy, Nginx needs to know the real visitor IP addresses. Configure Nginx to trust Cloudflare's IP ranges and use the CF-Connecting-IP header.

IP Ranges: Keep your Cloudflare IP list updated. Check the official lists at:

Create Cloudflare IP List

Navigate to includes directory
cd /etc/nginx/includes
Create IP list configuration
sudo nano cloudflare_ip_list.conf
Cloudflare IP ranges (Last updated October 2022)
# Last updated OCT 2022
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;
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;
real_ip_header CF-Connecting-IP;

Include in Server Block

Navigate to sites-available
cd /etc/nginx/sites-available/
Edit site configuration
sudo nano example.com.conf

Add this include directive in your server block:

Include Cloudflare IP list
include /etc/nginx/includes/cloudflare_ip_list.conf;
Test and reload
sudo nginx -t
sudo systemctl reload nginx

Cloudflare Request Flow

Visitor

Original IP: 1.2.3.4

Cloudflare

Proxy IP: 104.16.x.x

Nginx

Reads CF-Connecting-IP

Logs Real IP

1.2.3.4

Result: Nginx will now correctly log visitor IP addresses instead of Cloudflare's proxy IPs, essential for analytics, security, and access control.

Summary & Best Practices

Static Sites

  • FastCGI Caching
  • OPcache enabled
  • Cloudflare CDN
  • Optimized PHP-FPM

Dynamic Sites

  • WP Super Cache + Redis
  • OR W3 Total Cache (with built-in Redis)
  • OPcache enabled
  • System cron for WP-Cron

E-commerce Sites

  • W3 Total Cache (recommended)
  • Redis object cache
  • Proper cache exclusions
  • WooCommerce session handling

Performance Optimization Checklist

Optimization Impact Priority Complexity
Disable Post Revisions Medium Low Easy
Optimize Memory Limits High High Easy
Configure System Cron Medium Medium Easy
Enable OPcache High High Medium
Implement Page Caching Very High High Medium
Setup Redis Object Cache High High Medium
Optimize PHP-FPM High Medium Medium
Configure Cloudflare Medium Low Easy
Critical Reminders:
  • Always test configuration changes with sudo nginx -t before reloading
  • Keep backups of configuration files before making changes
  • Monitor server logs after implementing changes
  • Update Cloudflare IP ranges periodically
  • Adjust OPcache settings based on production vs development environment
  • Calculate PHP-FPM max_children based on available memory

Complete Caching Architecture

Cloudflare CDN

Edge caching

Nginx

Reverse proxy

Page Cache

Static HTML

PHP-FPM

OPcache

Redis

Object cache

MySQL

Database