2. Page Caching (FastCGI Cache)
2.1 How Page Caching Works
Page Cache Request Flow
Client Request
↓
NGINX: Check Cache
↓
Cache Hit? → Serve HTML
↓
Cache Miss? → PHP + DB
↓
Store in Cache & Serve
When a client requests a page, the web server first checks for a pre-built HTML copy in the FastCGI
cache. If found, it's immediately served to the client. If not found, the request is forwarded to PHP
for processing, which generates the HTML dynamically.
2.2 FastCGI Cache Configuration
Step 1: Configure NGINX HTTP Context
Navigate to NGINX configuration:
cd /etc/nginx
sudo nano nginx.conf
Add the following directives above the "Virtual Host Configs" comment in the HTTP context:
FastCGI Cache Directives (add to nginx.conf):
### 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;
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;
Important: Replace SITE with your site identifier and NAME
with a unique cache zone name for each site.
Step 2: Create Cache Exclusion Rules
Create exclusion file:
cd /etc/nginx/includes/
sudo nano fastcgi_cache_excludes.conf
Cache exclusion 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 Server Block
Edit server block:
cd /etc/nginx/sites-available
sudo nano example.com.conf
Add to server block:
include /etc/nginx/includes/fastcgi_cache_excludes.conf;
add_header X-FastCGI-Cache $upstream_cache_status;
Add to PHP location block:
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache NAME;
fastcgi_cache_valid 60m;
Step 4: Enable Cache Purging
Add purge location to server block:
location ~ /purge(/.*) {
fastcgi_cache_purge NAME "$scheme$request_method$host$1";
}
Step 5: Test and Reload
Test configuration and reload:
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm
Step 6: Verify Cache is Working
Check cache headers:
curl -I https://example.com
curl -I https://www.example.com
Success Indicator: Look for X-FastCGI-Cache: HIT in the response headers.
First request will show MISS, subsequent requests should show HIT.
2.3 Removing FastCGI Cache
Edit server block:
cd /etc/nginx/sites-available
sudo nano example.com.conf
Remove or comment out the following directives:
include /etc/nginx/includes/fastcgi_cache_excludes.conf;
add_header X-FastCGI-Cache $upstream_cache_status;
- All
fastcgi_cache directives in PHP location block
- The
/purge location block
Reload services:
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm
3. Object Caching (Redis)
3.1 Understanding Object Caching
Object caching stores the results of database queries in memory, eliminating the need to query the
database repeatedly for the same data. This is particularly beneficial for logged-in users who cannot
benefit from page caching.
Object Cache Architecture
WordPress Query
↓
Check Redis Cache
↓
Cache Hit? → Return from Memory
↓
Cache Miss? → Query Database
↓
Store in Redis & Return
Key Benefits:
- Reduces database load significantly
- Faster response times for logged-in users
- Persistent cache across page loads
- Handles complex database queries efficiently
3.2 Installing and Configuring Redis
Step 1: Install Redis and PHP Extension
Update and install packages:
sudo apt update
sudo apt install redis-server php8.3-redis
Step 2: Verify Redis Installation
Check Redis status:
sudo systemctl status redis-server
Check Redis logs:
sudo cat /var/log/redis/redis-server.log
Step 3: Fix Memory Overcommit Warning
If you see a warning about overcommit_memory in the logs, fix it as follows:
Create sysctl configuration:
cd /etc/sysctl.d/
sudo nano 11-redis.conf
Add this line:
vm.overcommit_memory = 1
Reboot system:
sudo reboot
Step 4: Configure Redis Memory Settings
Edit Redis configuration:
cd /etc/
sudo nano redis/redis.conf
Add/modify these lines:
maxmemory 256mb
maxmemory-policy allkeys-lru
Memory Policy Explanation: allkeys-lru means Redis will remove the least
recently used keys when memory limit is reached, keeping the most frequently accessed data in cache.
Restart Redis:
sudo systemctl restart redis-server
sudo reboot
3.3 Configure WordPress for Redis
Step 1: Set Cache Key Salt
Edit wp-config.php:
cd /var/www/example.com/public_html/
sudo nano wp-config.php
Add this line (before "That's all, stop editing!"):
define( 'WP_CACHE_KEY_SALT', 'example.com' );
Important: Use your actual domain name as the salt value to ensure cache keys are
unique to your site.
Step 2: Configure WooCommerce Exclusions (if applicable)
If you're running WooCommerce, prevent Redis from caching session data:
Add to wp-config.php:
/** PREVENT REDIS CACHING WOO SESSION DATA */
define('WP_REDIS_IGNORED_GROUPS', 'wc_session');
WooCommerce Cache Exclusions: Make sure to exclude these pages and cookies from
caching:
- Pages:
/cart/, /my-account/, /checkout/
- Cookies:
woocommerce_cart_hash, woocommerce_items_in_cart,
wp_woocommerce_session_
Step 3: Install Redis Object Cache Plugin
Install the "Redis Object Cache" plugin from the WordPress repository and activate it. Then click the
"Enable Object Cache" button in the plugin settings.
4. OPcode Caching (OPcache)
4.1 Understanding OPcode Cache
OPcode caching stores precompiled PHP script bytecode in memory. When PHP code is executed, it's first
compiled into opcode (machine-readable instructions). Without OPcache, this compilation happens every
time a script runs. With OPcache enabled, the compiled bytecode is stored in shared memory, bypassing
the compilation step for subsequent requests.
OPcode Cache Process
PHP Script Request
↓
Check OPcache
↓
Bytecode Cached? → Execute
↓
Not Cached? → Compile PHP
↓
Store Bytecode & Execute
4.2 Configuring OPcache
Step 1: Navigate to PHP-FPM Pool Configuration
Change to pool directory:
cd /etc/php/8.3/fpm/pool.d/
ls
Step 2: Edit Pool Configuration File
Open your site's pool config:
sudo nano example.com.conf
Step 3: Add OPcache Directives
Add the following directives to the end of your pool configuration file:
DEVELOPMENT SERVER Configuration:
; 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
PRODUCTION SERVER Configuration (commented out initially):
; 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
| Directive |
Purpose |
Recommended Value |
memory_consumption |
Memory allocated for OPcache |
256 MB |
interned_strings_buffer |
Memory for storing interned strings |
32 MB |
max_accelerated_files |
Maximum number of PHP files to cache |
20000 (adjust based on file count) |
validate_timestamps |
Check file modification times |
1 (dev), 0 (production) |
revalidate_freq |
How often to check timestamps (seconds) |
2 seconds |
Key Difference: The main difference between development and production configurations
is validate_timestamps. Set to 1 for development (checks for file changes) and 0 for
production (maximum performance).
Step 4: Reload PHP-FPM
Apply changes:
sudo systemctl reload php8.3-fpm
4.3 Monitoring OPcache File Count
Count PHP Files in Your Installation
Change to web root:
cd /var/www/
Count all PHP files:
sudo find . -type f -print | grep php | wc -l
Understanding max_accelerated_files:
- The value must be higher than your actual PHP file count
- PHP uses the first prime number ≥ your configured value
- Configured value of 20,000 → Actual value: 32,531 (next prime)
- Check this value after installing themes, plugins, or WordPress updates
- A single site with minimal plugins: ~1,500-2,000 files
- Multiple sites with commercial themes: 20,000-30,000+ files
Action Required: If your PHP file count approaches 25,000-30,000, increase
max_accelerated_files to 40,000. The actual cache size will automatically adjust to 65,537
(next prime number).
Verify Current OPcache Setting
Display current configuration:
cd /etc/php/8.3/fpm/pool.d/
sudo cat example.com.conf
5. Browser Caching
5.1 Understanding Browser Caching
Browser caching allows web browsers to store copies of web resources (HTML pages, CSS stylesheets,
JavaScript files, images, and other media files) locally on a user's device. When a user visits your
site, their browser saves certain elements in its cache. On subsequent visits, the browser retrieves
these elements from local storage instead of downloading them again from the server.
Benefits of Browser Caching:
- Faster page load times for returning visitors
- Reduced server bandwidth consumption
- Improved user experience with smooth browsing
- Lower server resource usage
5.2 Implementation
Browser caching is typically configured through NGINX server blocks using HTTP headers that specify how
long browsers should cache different types of files. This configuration is usually set up when creating
your NGINX server blocks and includes directives for various file types (images, CSS, JavaScript, fonts,
etc.).
Note: Browser caching configuration is implemented in NGINX server blocks during
initial setup. The cache duration is specified using expires directives and
Cache-Control headers for different file types.
6. WordPress Caching Plugin Configurations
6.1 WP Super Cache
Step 1: Create Cache Exclusion Rules
Create WP Super Cache exclusion file:
cd /etc/nginx/includes/
sudo nano wp_super_cache_excludes.conf
Add exclusion rules:
# 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';
}
Step 2: Configure Location Block
Add to exclusion file:
# 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 ;
}
Step 3: Include in Server Block
Edit server block:
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Add include directive:
include /etc/nginx/includes/wp_super_cache_exclusions.conf;
Reload NGINX:
sudo nginx -t
sudo systemctl reload nginx
Uninstalling WP Super Cache
Remove include directive:
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Remove: include /etc/nginx/includes/wp_super_cache_exclusions.conf;
Uncomment the 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
6.2 W3 Total Cache (W3TC)
Step 1: Prepare nginx.conf File
Create nginx.conf in public_html:
cd /var/www/example.com/public_html/
sudo touch nginx.conf
sudo chown username:username nginx.conf
sudo chmod 660 nginx.conf
sudo ls -l
Security: Replace username:username with your actual username. This file
will be automatically populated by W3TC.
Step 2: Secure nginx.conf File
Add to security directives:
cd /etc/nginx/includes/
sudo nano nginx_security_directives.conf
Add this line:
location = /nginx.conf { deny all; }
Step 3: Create W3TC Exclusion Rules
Create W3TC exclusion file:
cd /etc/nginx/includes/
sudo nano w3tc_cache_excludes.conf
Add exclusion rules:
# ---------------------
# 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 Server Block
Edit server block:
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Comment out the default location block and add includes:
#location / {
# try_files $uri $uri/ /index.php$is_args$args;
#}
include /etc/nginx/includes/w3tc_cache_excludes.conf;
include /var/www/example.com/public_html/nginx.conf;
Step 5: Install PHP Tidy Extension (for minification)
Install and reload:
sudo apt update
sudo apt install php8.3-tidy
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
Uninstalling W3 Total Cache
Edit server block:
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Uncomment the default location block and remove includes:
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
Remove these lines:
include /etc/nginx/includes/w3tc_cache_excludes.conf;
include /var/www/example.com/public_html/nginx.conf;
Remove cache directories:
cd /var/www/example.com/public_html/wp-content/
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
7. Additional Optimizations
7.1 WordPress Post Revisions
Disable or limit post revisions to reduce database size:
Edit wp-config.php:
cd /var/www/example.com/
sudo nano public_html/wp-config.php
Add before "That's all, stop editing!":
define('WP_POST_REVISIONS', 'false');
Restart PHP-FPM:
sudo systemctl restart php8.3-fpm
7.2 WordPress Memory Limit
PHP-FPM Pool Configuration
Edit pool configuration:
cd /etc/php/8.3/fpm/pool.d/
sudo nano example.com.conf
Set memory limit (uncomment and modify):
;php_admin_value[memory_limit] = 32M
php_admin_value[memory_limit] = 256M
WordPress Configuration
Add to wp-config.php:
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
7.3 WordPress Cron Management
Disable WP-Cron and use system cron for better performance:
Disable WP-Cron in wp-config.php:
define('DISABLE_WP_CRON', true);
Restart PHP-FPM:
sudo systemctl restart php8.3-fpm
Add system cron job:
crontab -e
Add this job (runs every 15 minutes):
*/15 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null
2>&1
7.4 PHP-FPM Process Manager Configuration
Calculate Average Memory per Process
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") }'
Configure OnDemand Process Manager
Edit pool configuration:
cd /etc/php/8.3/fpm/pool.d/
sudo nano example.com.conf
Add/modify these directives:
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 for max_children Warnings
Check PHP-FPM logs:
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" in your logs, increase the pm.max_children value.
7.5 Cloudflare Integration
If using Cloudflare, configure NGINX to recognize real client IP addresses:
Create Cloudflare IP list:
cd /etc/nginx/includes
sudo nano cloudflare_ip_list.conf
Add Cloudflare IP ranges:
# 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:
cd /etc/nginx/sites-available/
sudo nano example.com.conf
Add include directive:
include /etc/nginx/includes/cloudflare_ip_list.conf;
Reload NGINX:
sudo nginx -t
sudo systemctl reload nginx
Update Regularly: Cloudflare IP ranges can change. Check for updates at: