The Comprehensive Guide to WordPress Caching Strategies
Caching is the single most effective way to improve WordPress performance. By storing frequently accessed data in fast-access memory, caching can reduce page load times from seconds to milliseconds. However, implementing effective caching requires understanding multiple layers and choosing the right strategy for your specific needs.
This comprehensive guide covers every aspect of WordPress caching, from basic browser caching to advanced edge caching strategies. Whether you're running a small blog or a high-traffic e-commerce site, you'll learn how to implement caching that dramatically improves performance while maintaining dynamic functionality.
Table of Contents
- Understanding Caching Layers
- Page Caching Implementation
- Object Caching with Redis/Memcached
- Browser and CDN Caching
- Database Query Caching
- Advanced Caching Techniques
- Cache Management and Invalidation
Understanding Caching Layers
The WordPress Caching Stack
Effective caching involves multiple layers, each serving a specific purpose:
┌─────────────────────────────────────┐
│ Browser Cache │ ← Fastest (0ms)
│ (User's Device) │
├─────────────────────────────────────┤
│ CDN Cache │ ← Very Fast (10-50ms)
│ (Global Edge Servers) │
├─────────────────────────────────────┤
│ Page Cache │ ← Fast (1-5ms)
│ (Nginx/Varnish/Plugin) │
├─────────────────────────────────────┤
│ Object Cache │ ← Fast (1-2ms)
│ (Redis/Memcached) │
├─────────────────────────────────────┤
│ OpCode Cache │ ← Very Fast (0.1ms)
│ (OPcache) │
├─────────────────────────────────────┤
│ Database Cache │ ← Moderate (1-10ms)
│ (MySQL Query Cache) │
└─────────────────────────────────────┘
How Caching Works
// Basic caching concept
function get_expensive_data($key) {
// Check cache first
$cached_data = wp_cache_get($key, 'my_cache_group');
if ($cached_data !== false) {
return $cached_data; // Cache hit!
}
// Cache miss - compute expensive operation
$data = perform_expensive_operation();
// Store in cache for next time
wp_cache_set($key, $data, 'my_cache_group', 3600); // Cache for 1 hour
return $data;
}
Cache Hit Rates and Performance
Cache Type | Typical Hit Rate | Performance Impact |
---|---|---|
Browser Cache | 80-90% | Eliminates request entirely |
CDN Cache | 70-85% | Reduces latency significantly |
Page Cache | 60-80% | Bypasses PHP/WordPress |
Object Cache | 85-95% | Speeds up database queries |
OpCode Cache | 95-99% | Eliminates PHP compilation |
Page Caching Implementation
Server-Level Page Caching with Nginx
# Nginx FastCGI cache configuration
http {
# Define cache zone
fastcgi_cache_path /var/cache/nginx levels=1:2
keys_zone=WORDPRESS:100m
inactive=60m
max_size=1g;
# Cache key to use
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# Cache status header
add_header X-Cache-Status $upstream_cache_status;
}
server {
listen 80;
server_name example.com;
root /var/www/wordpress;
# Cache bypass conditions
set $skip_cache 0;
# POST requests and URLs with query string
if ($request_method = POST) {
set $skip_cache 1;
}
if ($query_string != "") {
set $skip_cache 1;
}
# Don't cache these pages
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|sitemap(_index)?.xml") {
set $skip_cache 1;
}
# Don't cache for logged in users
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
set $skip_cache 1;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
# FastCGI cache settings
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 301 302 60m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_use_stale error timeout updating invalid_header http_500;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
# Cache purging
fastcgi_cache_purge $purge_method;
}
}
Plugin-Based Page Caching
// Custom page caching implementation
class Advanced_Page_Cache {
private $cache_dir;
private $cache_time = 3600;
private $excluded_urls = [];
public function __construct() {
$this->cache_dir = WP_CONTENT_DIR . '/cache/pages/';
$this->excluded_urls = [
'/wp-admin/',
'/wp-login.php',
'/cart/',
'/checkout/',
'/my-account/'
];
// Only cache for non-logged-in users
if (!is_user_logged_in() && !is_admin()) {
add_action('init', [$this, 'start_cache'], 1);
add_action('shutdown', [$this, 'end_cache'], 999);
}
// Cache clearing hooks
add_action('save_post', [$this, 'clear_post_cache']);
add_action('comment_post', [$this, 'clear_comment_cache']);
add_action('switch_theme', [$this, 'clear_all_cache']);
}
public function start_cache() {
if ($this->should_cache()) {
$cache_file = $this->get_cache_file();
if ($this->is_cache_valid($cache_file)) {
$this->serve_cache($cache_file);
exit;
}
ob_start();
}
}
public function end_cache() {
if ($this->should_cache() && ob_get_level() > 0) {
$content = ob_get_contents();
if (!empty($content)) {
$this->save_cache($content);
}
}
}
private function should_cache() {
// Don't cache POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
return false;
}
// Don't cache if there are GET parameters (except allowed ones)
$allowed_params = ['utm_source', 'utm_medium', 'utm_campaign'];
$query_params = array_diff(array_keys($_GET), $allowed_params);
if (!empty($query_params)) {
return false;
}
// Check excluded URLs
$current_url = $_SERVER['REQUEST_URI'];
foreach ($this->excluded_urls as $excluded) {
if (strpos($current_url, $excluded) !== false) {
return false;
}
}
// Don't cache 404s
if (is_404()) {
return false;
}
return true;
}
private function get_cache_file() {
$url = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$hash = md5($url);
// Create subdirectory based on first 2 characters of hash
$subdir = substr($hash, 0, 2);
$dir = $this->cache_dir . $subdir . '/';
if (!file_exists($dir)) {
wp_mkdir_p($dir);
}
return $dir . $hash . '.html';
}
private function is_cache_valid($cache_file) {
if (!file_exists($cache_file)) {
return false;
}
$file_time = filemtime($cache_file);
$current_time = time();
return ($current_time - $file_time) < $this->cache_time;
}
private function serve_cache($cache_file) {
// Set cache headers
header('X-Cache: HIT');
header('X-Cache-Time: ' . date('Y-m-d H:i:s', filemtime($cache_file)));
// Serve gzipped content if supported
if (function_exists('gzencode') &&
isset($_SERVER['HTTP_ACCEPT_ENCODING']) &&
strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
$gzip_file = $cache_file . '.gz';
if (file_exists($gzip_file)) {
header('Content-Encoding: gzip');
readfile($gzip_file);
} else {
readfile($cache_file);
}
} else {
readfile($cache_file);
}
}
private function save_cache($content) {
$cache_file = $this->get_cache_file();
// Add cache information comment
$cache_info = sprintf(
"\n<!-- Cached on %s -->\n<!-- Cache expires on %s -->",
date('Y-m-d H:i:s'),
date('Y-m-d H:i:s', time() + $this->cache_time)
);
$content .= $cache_info;
// Save regular version
file_put_contents($cache_file, $content);
// Save gzipped version
if (function_exists('gzencode')) {
$gzipped = gzencode($content, 9);
file_put_contents($cache_file . '.gz', $gzipped);
}
}
public function clear_post_cache($post_id) {
if (wp_is_post_revision($post_id)) {
return;
}
$post = get_post($post_id);
if ($post->post_status !== 'publish') {
return;
}
// Clear specific URLs
$urls_to_clear = [
get_permalink($post_id),
home_url('/'),
get_post_type_archive_link($post->post_type)
];
// Add category and tag pages
$taxonomies = get_object_taxonomies($post->post_type);
foreach ($taxonomies as $taxonomy) {
$terms = wp_get_object_terms($post_id, $taxonomy);
foreach ($terms as $term) {
$urls_to_clear[] = get_term_link($term);
}
}
foreach ($urls_to_clear as $url) {
$this->clear_url_cache($url);
}
}
private function clear_url_cache($url) {
$parsed = parse_url($url);
$cache_url = $parsed['host'] . $parsed['path'];
$hash = md5($cache_url);
$subdir = substr($hash, 0, 2);
$files = [
$this->cache_dir . $subdir . '/' . $hash . '.html',
$this->cache_dir . $subdir . '/' . $hash . '.html.gz'
];
foreach ($files as $file) {
if (file_exists($file)) {
unlink($file);
}
}
}
public function clear_all_cache() {
$this->recursive_rmdir($this->cache_dir);
wp_mkdir_p($this->cache_dir);
}
private function recursive_rmdir($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir . "/" . $object)) {
$this->recursive_rmdir($dir . "/" . $object);
} else {
unlink($dir . "/" . $object);
}
}
}
rmdir($dir);
}
}
}
// Initialize page cache
new Advanced_Page_Cache();
Varnish Configuration
# Varnish VCL for WordPress
vcl 4.0;
import std;
backend default {
.host = "127.0.0.1";
.port = "8080";
.connect_timeout = 600s;
.first_byte_timeout = 600s;
.between_bytes_timeout = 600s;
}
# ACL for purging
acl purge {
"localhost";
"127.0.0.1";
"::1";
}
sub vcl_recv {
# Normalize the host header
if (req.http.host ~ "(?i)^(www.)?example.com") {
set req.http.host = "example.com";
}
# Allow purging
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return (synth(405, "Not allowed."));
}
return (purge);
}
# Normalize the query string
set req.url = std.querysort(req.url);
# Remove tracking parameters
if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsub(req.url, "\?$", "");
}
# Remove cookies for static files
if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
unset req.http.Cookie;
return (hash);
}
# Don't cache WordPress admin
if (req.url ~ "wp-admin|wp-login|preview=true") {
return (pass);
}
# Don't cache these pages
if (req.url ~ "/(cart|my-account|checkout|addons)") {
return (pass);
}
# Remove cookies for cached pages
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(wordpress_logged_in_|wp-postpass_|comment_author_)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
unset req.http.Cookie;
}
}
}
sub vcl_backend_response {
# Set grace period
set beresp.grace = 6h;
# Set cache time for different content types
if (bereq.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
unset beresp.http.set-cookie;
set beresp.ttl = 1y;
}
# Cache HTML for 10 minutes
if (beresp.http.content-type ~ "text/html") {
set beresp.ttl = 10m;
}
}
sub vcl_deliver {
# Add cache hit/miss header
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
}
Object Caching with Redis/Memcached
Redis Implementation
// Redis object cache drop-in (object-cache.php)
class WP_Object_Cache {
private $redis;
private $connected = false;
private $cache = [];
private $global_groups = [];
private $non_persistent_groups = [];
private $multisite = false;
private $blog_prefix;
public function __construct() {
$this->multisite = is_multisite();
$this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : '';
$this->connect();
// Define global groups
$this->global_groups = [
'users', 'userlogins', 'usermeta', 'user_meta',
'site-transient', 'site-options', 'site-lookup',
'blog-lookup', 'blog-details', 'rss',
'global-cache-test', 'networks', 'sites'
];
// Define non-persistent groups
$this->non_persistent_groups = ['counts', 'plugins'];
}
private function connect() {
try {
$this->redis = new Redis();
// Connection parameters
$host = defined('WP_REDIS_HOST') ? WP_REDIS_HOST : '127.0.0.1';
$port = defined('WP_REDIS_PORT') ? WP_REDIS_PORT : 6379;
$timeout = defined('WP_REDIS_TIMEOUT') ? WP_REDIS_TIMEOUT : 1;
// Connect
$this->connected = $this->redis->connect($host, $port, $timeout);
// Authentication
if ($this->connected && defined('WP_REDIS_PASSWORD')) {
$this->connected = $this->redis->auth(WP_REDIS_PASSWORD);
}
// Select database
if ($this->connected && defined('WP_REDIS_DATABASE')) {
$this->connected = $this->redis->select(WP_REDIS_DATABASE);
}
// Set serialization
if ($this->connected) {
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
}
} catch (Exception $e) {
$this->connected = false;
error_log('Redis connection failed: ' . $e->getMessage());
}
}
public function get($key, $group = 'default', $force = false, &$found = null) {
if (!$this->connected) {
$found = false;
return false;
}
$derived_key = $this->build_key($key, $group);
// Check local cache first
if (isset($this->cache[$derived_key]) && !$force) {
$found = true;
return $this->cache[$derived_key];
}
// Non-persistent groups
if (in_array($group, $this->non_persistent_groups)) {
$found = false;
return false;
}
try {
$value = $this->redis->get($derived_key);
if ($value === false) {
$found = false;
return false;
}
$this->cache[$derived_key] = $value;
$found = true;
return $value;
} catch (Exception $e) {
$found = false;
return false;
}
}
public function set($key, $data, $group = 'default', $expire = 0) {
if (!$this->connected) {
return false;
}
$derived_key = $this->build_key($key, $group);
// Store in local cache
$this->cache[$derived_key] = $data;
// Non-persistent groups
if (in_array($group, $this->non_persistent_groups)) {
return true;
}
try {
if ($expire > 0) {
return $this->redis->setex($derived_key, $expire, $data);
} else {
return $this->redis->set($derived_key, $data);
}
} catch (Exception $e) {
return false;
}
}
public function delete($key, $group = 'default') {
if (!$this->connected) {
return false;
}
$derived_key = $this->build_key($key, $group);
unset($this->cache[$derived_key]);
if (in_array($group, $this->non_persistent_groups)) {
return true;
}
try {
return $this->redis->del($derived_key) > 0;
} catch (Exception $e) {
return false;
}
}
public function flush() {
$this->cache = [];
if (!$this->connected) {
return false;
}
try {
if ($this->multisite) {
// Flush only current site's keys
$pattern = $this->blog_prefix . '*';
$keys = $this->redis->keys($pattern);
if (!empty($keys)) {
return $this->redis->del($keys) > 0;
}
return true;
} else {
return $this->redis->flushDB();
}
} catch (Exception $e) {
return false;
}
}
public function add($key, $data, $group = 'default', $expire = 0) {
if (!$this->connected) {
return false;
}
if ($this->get($key, $group) !== false) {
return false;
}
return $this->set($key, $data, $group, $expire);
}
public function replace($key, $data, $group = 'default', $expire = 0) {
if (!$this->connected) {
return false;
}
if ($this->get($key, $group) === false) {
return false;
}
return $this->set($key, $data, $group, $expire);
}
public function incr($key, $offset = 1, $group = 'default') {
if (!$this->connected) {
return false;
}
$derived_key = $this->build_key($key, $group);
try {
$value = $this->redis->incrBy($derived_key, $offset);
$this->cache[$derived_key] = $value;
return $value;
} catch (Exception $e) {
return false;
}
}
public function decr($key, $offset = 1, $group = 'default') {
return $this->incr($key, -$offset, $group);
}
private function build_key($key, $group = 'default') {
if (empty($group)) {
$group = 'default';
}
$prefix = in_array($group, $this->global_groups) ? '' : $this->blog_prefix;
return WP_CACHE_KEY_SALT . $prefix . $group . ':' . $key;
}
public function stats() {
if (!$this->connected) {
return [];
}
try {
$info = $this->redis->info();
return [
'hits' => isset($info['keyspace_hits']) ? $info['keyspace_hits'] : 0,
'misses' => isset($info['keyspace_misses']) ? $info['keyspace_misses'] : 0,
'uptime' => isset($info['uptime_in_seconds']) ? $info['uptime_in_seconds'] : 0,
'memory_used' => isset($info['used_memory_human']) ? $info['used_memory_human'] : '0B'
];
} catch (Exception $e) {
return [];
}
}
public function close() {
if ($this->connected && $this->redis) {
try {
$this->redis->close();
} catch (Exception $e) {
// Ignore close errors
}
}
$this->connected = false;
}
public function __destruct() {
$this->close();
}
}
// Initialize global cache object
$GLOBALS['wp_object_cache'] = new WP_Object_Cache();
Memcached Implementation
// Memcached configuration
class Memcached_Cache {
private $memcached;
private $servers = [];
public function __construct() {
$this->servers = [
['127.0.0.1', 11211, 100], // host, port, weight
];
$this->connect();
}
private function connect() {
$this->memcached = new Memcached();
// Set options
$this->memcached->setOptions([
Memcached::OPT_COMPRESSION => false,
Memcached::OPT_SERIALIZER => Memcached::SERIALIZER_PHP,
Memcached::OPT_PREFIX_KEY => WP_CACHE_KEY_SALT,
Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
Memcached::OPT_NO_BLOCK => true,
Memcached::OPT_TCP_NODELAY => true,
Memcached::OPT_CONNECT_TIMEOUT => 2000, // 2 seconds
Memcached::OPT_RETRY_TIMEOUT => 2,
Memcached::OPT_SERVER_FAILURE_LIMIT => 3,
]);
// Add servers
foreach ($this->servers as $server) {
$this->memcached->addServer($server[0], $server[1], $server[2]);
}
}
public function get($key, $group = 'default') {
$derived_key = $this->build_key($key, $group);
return $this->memcached->get($derived_key);
}
public function set($key, $data, $group = 'default', $expire = 0) {
$derived_key = $this->build_key($key, $group);
return $this->memcached->set($derived_key, $data, $expire);
}
private function build_key($key, $group) {
return $group . ':' . $key;
}
}
Browser and CDN Caching
Browser Cache Headers
// Advanced browser caching implementation
class Browser_Cache_Headers {
public function __construct() {
add_action('send_headers', [$this, 'set_cache_headers']);
add_filter('wp_headers', [$this, 'modify_headers']);
add_action('wp_head', [$this, 'add_cache_meta_tags'], 1);
}
public function set_cache_headers() {
if (is_admin() || is_user_logged_in()) {
return;
}
// Different cache times for different content types
$cache_times = [
'text/html' => 600, // 10 minutes
'text/css' => 31536000, // 1 year
'application/javascript' => 31536000, // 1 year
'image/jpeg' => 31536000, // 1 year
'image/png' => 31536000, // 1 year
'image/gif' => 31536000, // 1 year
'image/webp' => 31536000, // 1 year
'font/woff2' => 31536000, // 1 year
];
// Get content type
$content_type = $this->get_content_type();
$cache_time = isset($cache_times[$content_type]) ? $cache_times[$content_type] : 3600;
// Set cache headers
header('Cache-Control: public, max-age=' . $cache_time);
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $cache_time) . ' GMT');
// ETag generation
if (!is_404()) {
$etag = $this->generate_etag();
header('ETag: "' . $etag . '"');
// Check If-None-Match
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $etag) {
header('HTTP/1.1 304 Not Modified');
exit;
}
}
// Add Vary header for dynamic content
if ($content_type === 'text/html') {
header('Vary: Accept-Encoding, Cookie');
} else {
header('Vary: Accept-Encoding');
}
}
private function get_content_type() {
$headers = headers_list();
foreach ($headers as $header) {
if (stripos($header, 'Content-Type:') === 0) {
$parts = explode(':', $header);
$content_type = trim($parts[1]);
return explode(';', $content_type)[0];
}
}
return 'text/html';
}
private function generate_etag() {
// Generate ETag based on content
$factors = [
get_queried_object_id(),
wp_get_theme()->get('Version'),
get_option('posts_last_modified', ''),
is_user_logged_in() ? 'logged-in' : 'guest'
];
return md5(implode('-', $factors));
}
public function modify_headers($headers) {
// Security headers
$headers['X-Content-Type-Options'] = 'nosniff';
$headers['X-Frame-Options'] = 'SAMEORIGIN';
$headers['X-XSS-Protection'] = '1; mode=block';
$headers['Referrer-Policy'] = 'strict-origin-when-cross-origin';
// Remove unnecessary headers
unset($headers['X-Powered-By']);
return $headers;
}
public function add_cache_meta_tags() {
if (is_singular()) {
$post = get_post();
?>
<!-- Cache meta tags -->
<meta http-equiv="last-modified" content="<?php echo get_the_modified_date('c', $post); ?>">
<meta http-equiv="cache-control" content="public">
<?php
}
}
}
new Browser_Cache_Headers();
CDN Integration
// CDN URL rewriting and optimization
class CDN_Integration {
private $cdn_url;
private $site_url;
private $directories = ['wp-content', 'wp-includes'];
private $extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'css', 'js', 'woff', 'woff2', 'ttf', 'svg'];
public function __construct() {
$this->cdn_url = defined('CDN_URL') ? CDN_URL : '';
$this->site_url = get_option('siteurl');
if (!empty($this->cdn_url)) {
add_filter('wp_get_attachment_url', [$this, 'rewrite_attachment_url']);
add_filter('style_loader_src', [$this, 'rewrite_asset_url']);
add_filter('script_loader_src', [$this, 'rewrite_asset_url']);
add_filter('the_content', [$this, 'rewrite_content_urls']);
add_filter('widget_text', [$this, 'rewrite_content_urls']);
// Srcset for responsive images
add_filter('wp_calculate_image_srcset', [$this, 'rewrite_srcset'], 10, 5);
}
}
public function rewrite_attachment_url($url) {
if ($this->should_rewrite($url)) {
$url = str_replace($this->site_url, $this->cdn_url, $url);
}
return $url;
}
public function rewrite_asset_url($url) {
if ($this->should_rewrite($url)) {
$url = str_replace($this->site_url, $this->cdn_url, $url);
// Add version string for cache busting
if (strpos($url, '?ver=') === false) {
$version = $this->get_asset_version($url);
$url .= '?ver=' . $version;
}
}
return $url;
}
public function rewrite_content_urls($content) {
if (is_admin() || is_feed()) {
return $content;
}
// Build regex pattern
$dirs = implode('|', $this->directories);
$exts = implode('|', $this->extensions);
$pattern = '#(' . quotemeta($this->site_url) . ')?/((' . $dirs . ')/.+\.(' . $exts . '))#';
$content = preg_replace($pattern, $this->cdn_url . '/$2', $content);
return $content;
}
public function rewrite_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id) {
foreach ($sources as &$source) {
if ($this->should_rewrite($source['url'])) {
$source['url'] = str_replace($this->site_url, $this->cdn_url, $source['url']);
}
}
return $sources;
}
private function should_rewrite($url) {
// Don't rewrite external URLs
if (strpos($url, $this->site_url) !== 0) {
return false;
}
// Don't rewrite admin URLs
if (strpos($url, '/wp-admin/') !== false) {
return false;
}
// Check if URL contains allowed directory
$has_dir = false;
foreach ($this->directories as $dir) {
if (strpos($url, '/' . $dir . '/') !== false) {
$has_dir = true;
break;
}
}
if (!$has_dir) {
return false;
}
// Check extension
$extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION);
return in_array(strtolower($extension), $this->extensions);
}
private function get_asset_version($url) {
// For theme assets, use theme version
if (strpos($url, get_template_directory_uri()) !== false) {
return wp_get_theme()->get('Version');
}
// For plugins, try to get plugin version
if (strpos($url, '/wp-content/plugins/') !== false) {
preg_match('#/wp-content/plugins/([^/]+)/#', $url, $matches);
if (!empty($matches[1])) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $matches[1] . '/' . $matches[1] . '.php');
if (!empty($plugin_data['Version'])) {
return $plugin_data['Version'];
}
}
}
// Default to file modification time
$file_path = str_replace($this->site_url, ABSPATH, $url);
$file_path = parse_url($file_path, PHP_URL_PATH);
if (file_exists($file_path)) {
return filemtime($file_path);
}
return time();
}
}
// Initialize CDN integration
new CDN_Integration();
Database Query Caching
Query Result Caching
// Advanced database query caching
class Query_Cache {
private $cache_group = 'query_cache';
private $cache_time = 3600; // 1 hour default
public function cache_query($sql, $callback, $cache_time = null) {
$cache_key = 'query_' . md5($sql);
$cache_time = $cache_time ?: $this->cache_time;
// Try to get from cache
$result = wp_cache_get($cache_key, $this->cache_group);
if ($result === false) {
// Execute query
$result = call_user_func($callback);
// Cache result
wp_cache_set($cache_key, $result, $this->cache_group, $cache_time);
}
return $result;
}
public function get_posts_with_meta($args = [], $cache_time = 3600) {
$defaults = [
'posts_per_page' => 10,
'post_status' => 'publish'
];
$args = wp_parse_args($args, $defaults);
$cache_key = 'posts_meta_' . md5(serialize($args));
return $this->cache_query($cache_key, function() use ($args) {
global $wpdb;
// Build optimized query
$query = new WP_Query($args);
$posts = $query->posts;
if (empty($posts)) {
return [];
}
// Get all post IDs
$post_ids = wp_list_pluck($posts, 'ID');
// Batch load all meta data
update_meta_cache('post', $post_ids);
// Batch load all terms
update_object_term_cache($post_ids, 'post');
return $posts;
}, $cache_time);
}
public function get_popular_posts($days = 7, $limit = 10) {
$cache_key = "popular_posts_{$days}_{$limit}";
return $this->cache_query($cache_key, function() use ($days, $limit) {
global $wpdb;
$sql = $wpdb->prepare("
SELECT p.*, COUNT(pm.meta_id) as view_count
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
WHERE p.post_status = 'publish'
AND p.post_type = 'post'
AND pm.meta_key = 'post_views'
AND p.post_date > DATE_SUB(NOW(), INTERVAL %d DAY)
GROUP BY p.ID
ORDER BY view_count DESC
LIMIT %d
", $days, $limit);
return $wpdb->get_results($sql);
}, 3600); // Cache for 1 hour
}
public function clear_query_cache($type = 'all') {
if ($type === 'all') {
wp_cache_flush_group($this->cache_group);
} else {
// Clear specific cache types
$patterns = [
'posts' => 'posts_*',
'popular' => 'popular_posts_*',
'query' => 'query_*'
];
if (isset($patterns[$type])) {
// If using Redis/Memcached with pattern support
wp_cache_delete_by_pattern($patterns[$type], $this->cache_group);
}
}
}
}
// Usage
$query_cache = new Query_Cache();
// Cache complex query
$popular_posts = $query_cache->get_popular_posts(30, 5);
// Cache custom query
$results = $query_cache->cache_query('custom_key', function() {
global $wpdb;
return $wpdb->get_results("SELECT * FROM custom_table WHERE status = 'active'");
}, 7200); // Cache for 2 hours
Advanced Caching Techniques
Fragment Caching
// Fragment caching for expensive operations
class Fragment_Cache {
public static function get($key, $callback, $expiration = 3600) {
$fragment = wp_cache_get($key, 'fragments');
if ($fragment === false) {
ob_start();
call_user_func($callback);
$fragment = ob_get_clean();
wp_cache_set($key, $fragment, 'fragments', $expiration);
}
return $fragment;
}
public static function output($key, $callback, $expiration = 3600) {
echo self::get($key, $callback, $expiration);
}
public static function delete($key) {
return wp_cache_delete($key, 'fragments');
}
public static function flush() {
return wp_cache_flush_group('fragments');
}
}
// Usage in templates
Fragment_Cache::output('sidebar_recent_posts', function() {
$recent_posts = new WP_Query([
'posts_per_page' => 5,
'no_found_rows' => true,
'update_post_term_cache' => false,
'update_post_meta_cache' => false
]);
if ($recent_posts->have_posts()) {
echo '<ul class="recent-posts">';
while ($recent_posts->have_posts()) {
$recent_posts->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
}
echo '</ul>';
}
wp_reset_postdata();
}, 3600);
Edge Side Includes (ESI)
// ESI implementation for dynamic content in cached pages
class ESI_Cache {
private $esi_enabled = false;
public function __construct() {
// Check if behind Varnish or CDN that supports ESI
$this->esi_enabled = isset($_SERVER['HTTP_SURROGATE_CAPABILITY']) &&
strpos($_SERVER['HTTP_SURROGATE_CAPABILITY'], 'ESI/1.0') !== false;
add_shortcode('esi', [$this, 'esi_shortcode']);
add_action('init', [$this, 'register_esi_endpoints']);
}
public function esi_shortcode($atts) {
$atts = shortcode_atts([
'src' => '',
'ttl' => 300,
'fallback' => ''
], $atts);
if (empty($atts['src'])) {
return '';
}
if ($this->esi_enabled) {
// Return ESI tag for Varnish
return sprintf(
'<esi:include src="%s" onerror="continue"/>',
esc_url($atts['src'])
);
} else {
// Fallback: fetch content directly
$response = wp_remote_get($atts['src']);
if (!is_wp_error($response)) {
return wp_remote_retrieve_body($response);
}
return $atts['fallback'];
}
}
public function register_esi_endpoints() {
// Register custom endpoints for ESI fragments
add_rewrite_rule(
'^esi/([^/]+)/?$',
'index.php?esi_fragment=$matches[1]',
'top'
);
add_filter('query_vars', function($vars) {
$vars[] = 'esi_fragment';
return $vars;
});
add_action('template_redirect', [$this, 'handle_esi_request']);
}
public function handle_esi_request() {
$fragment = get_query_var('esi_fragment');
if (!$fragment) {
return;
}
// Set appropriate cache headers
header('Cache-Control: public, max-age=300');
header('Surrogate-Control: max-age=300');
// Output fragment based on type
switch ($fragment) {
case 'user-menu':
$this->output_user_menu();
break;
case 'cart-count':
$this->output_cart_count();
break;
case 'recent-comments':
$this->output_recent_comments();
break;
}
exit;
}
private function output_user_menu() {
if (is_user_logged_in()) {
$user = wp_get_current_user();
echo '<div class="user-menu">';
echo 'Welcome, ' . esc_html($user->display_name);
echo ' | <a href="' . wp_logout_url() . '">Logout</a>';
echo '</div>';
} else {
echo '<div class="user-menu">';
echo '<a href="' . wp_login_url() . '">Login</a>';
echo ' | <a href="' . wp_registration_url() . '">Register</a>';
echo '</div>';
}
}
private function output_cart_count() {
if (function_exists('WC')) {
$count = WC()->cart->get_cart_contents_count();
echo '<span class="cart-count">' . $count . '</span>';
}
}
private function output_recent_comments() {
$comments = get_comments([
'number' => 5,
'status' => 'approve'
]);
echo '<ul class="recent-comments">';
foreach ($comments as $comment) {
echo '<li>';
echo esc_html($comment->comment_author) . ' on ';
echo '<a href="' . get_comment_link($comment) . '">';
echo get_the_title($comment->comment_post_ID);
echo '</a>';
echo '</li>';
}
echo '</ul>';
}
}
new ESI_Cache();
Cache Management and Invalidation
Smart Cache Invalidation
// Intelligent cache invalidation system
class Cache_Invalidation {
private $purge_urls = [];
public function __construct() {
// Post changes
add_action('save_post', [$this, 'purge_post_cache'], 10, 3);
add_action('deleted_post', [$this, 'purge_post_cache'], 10, 2);
add_action('trash_post', [$this, 'purge_post_cache']);
add_action('publish_future_post', [$this, 'purge_post_cache']);
// Comments
add_action('comment_post', [$this, 'purge_comment_cache'], 10, 2);
add_action('edit_comment', [$this, 'purge_comment_cache']);
add_action('delete_comment', [$this, 'purge_comment_cache']);
// Terms
add_action('created_term', [$this, 'purge_term_cache'], 10, 3);
add_action('edited_term', [$this, 'purge_term_cache'], 10, 3);
add_action('delete_term', [$this, 'purge_term_cache'], 10, 4);
// Theme changes
add_action('switch_theme', [$this, 'purge_all_cache']);
add_action('customize_save', [$this, 'purge_all_cache']);
// Execute purges at shutdown
add_action('shutdown', [$this, 'execute_purges']);
}
public function purge_post_cache($post_id, $post = null, $update = false) {
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
return;
}
if (!$post) {
$post = get_post($post_id);
}
if (!$post || $post->post_status !== 'publish') {
return;
}
// Add URLs to purge
$this->add_purge_url(get_permalink($post_id));
$this->add_purge_url(home_url('/'));
// Post type archive
$post_type_archive = get_post_type_archive_link($post->post_type);
if ($post_type_archive) {
$this->add_purge_url($post_type_archive);
}
// Author archive
$this->add_purge_url(get_author_posts_url($post->post_author));
// Date archives
$this->add_purge_url(get_year_link(get_the_date('Y', $post)));
$this->add_purge_url(get_month_link(get_the_date('Y', $post), get_the_date('m', $post)));
$this->add_purge_url(get_day_link(get_the_date('Y', $post), get_the_date('m', $post), get_the_date('d', $post)));
// Taxonomies
$taxonomies = get_object_taxonomies($post->post_type);
foreach ($taxonomies as $taxonomy) {
$terms = wp_get_object_terms($post_id, $taxonomy);
foreach ($terms as $term) {
$this->add_purge_url(get_term_link($term));
}
}
// Related posts (if using a specific meta key)
$related_ids = get_post_meta($post_id, '_related_posts', true);
if (!empty($related_ids)) {
foreach ($related_ids as $related_id) {
$this->add_purge_url(get_permalink($related_id));
}
}
// Pagination pages
$this->add_pagination_urls(home_url('/'));
// Feed URLs
$this->add_purge_url(get_bloginfo('rss2_url'));
$this->add_purge_url(get_bloginfo('atom_url'));
}
public function purge_comment_cache($comment_id, $comment_approved = 1) {
$comment = get_comment($comment_id);
if (!$comment || $comment_approved !== 1) {
return;
}
// Purge post page
$this->add_purge_url(get_permalink($comment->comment_post_ID));
// Purge recent comments widgets
$this->purge_fragment_cache('widget_recent_comments');
}
public function purge_term_cache($term_id, $tt_id, $taxonomy) {
$term = get_term($term_id, $taxonomy);
if (!$term || is_wp_error($term)) {
return;
}
// Term archive
$this->add_purge_url(get_term_link($term));
// Posts in this term
$posts = get_posts([
'tax_query' => [
[
'taxonomy' => $taxonomy,
'terms' => $term_id
]
],
'posts_per_page' => -1,
'fields' => 'ids'
]);
foreach ($posts as $post_id) {
$this->add_purge_url(get_permalink($post_id));
}
}
public function purge_all_cache() {
// Clear all caches
if (function_exists('wp_cache_flush')) {
wp_cache_flush();
}
// Clear page cache
$this->clear_page_cache_files();
// Clear CDN cache
$this->purge_cdn_cache('/*');
// Clear Varnish cache
$this->purge_varnish_cache();
}
private function add_purge_url($url) {
if (!empty($url)) {
$this->purge_urls[] = $url;
}
}
private function add_pagination_urls($base_url) {
global $wp_rewrite;
// Add first 5 pagination pages
for ($i = 2; $i <= 5; $i++) {
if ($wp_rewrite->using_permalinks()) {
$this->add_purge_url($base_url . 'page/' . $i . '/');
} else {
$this->add_purge_url(add_query_arg('paged', $i, $base_url));
}
}
}
public function execute_purges() {
if (empty($this->purge_urls)) {
return;
}
// Remove duplicates
$this->purge_urls = array_unique($this->purge_urls);
// Purge each URL
foreach ($this->purge_urls as $url) {
$this->purge_url($url);
}
// Clear URLs after purging
$this->purge_urls = [];
}
private function purge_url($url) {
// Purge from various cache layers
// 1. Page cache files
$this->purge_page_cache_file($url);
// 2. Varnish cache
$this->purge_varnish_url($url);
// 3. CDN cache
$this->purge_cdn_url($url);
// 4. Fragment cache
$cache_key = 'page_' . md5($url);
wp_cache_delete($cache_key, 'pages');
}
private function purge_page_cache_file($url) {
$cache_dir = WP_CONTENT_DIR . '/cache/pages/';
$parsed = parse_url($url);
$path = $parsed['host'] . $parsed['path'];
$hash = md5($path);
$subdir = substr($hash, 0, 2);
$files = [
$cache_dir . $subdir . '/' . $hash . '.html',
$cache_dir . $subdir . '/' . $hash . '.html.gz'
];
foreach ($files as $file) {
if (file_exists($file)) {
unlink($file);
}
}
}
private function purge_varnish_url($url) {
if (!defined('VARNISH_SERVERS')) {
return;
}
$servers = explode(',', VARNISH_SERVERS);
foreach ($servers as $server) {
wp_remote_request($url, [
'method' => 'PURGE',
'headers' => [
'Host' => parse_url($url, PHP_URL_HOST)
],
'sslverify' => false,
'timeout' => 1
]);
}
}
private function purge_cdn_url($url) {
// Example: Cloudflare purge
if (defined('CLOUDFLARE_ZONE_ID') && defined('CLOUDFLARE_API_KEY')) {
$api_url = 'https://api.cloudflare.com/client/v4/zones/' . CLOUDFLARE_ZONE_ID . '/purge_cache';
wp_remote_post($api_url, [
'headers' => [
'X-Auth-Email' => CLOUDFLARE_EMAIL,
'X-Auth-Key' => CLOUDFLARE_API_KEY,
'Content-Type' => 'application/json'
],
'body' => json_encode([
'files' => [$url]
])
]);
}
}
private function clear_page_cache_files() {
$cache_dir = WP_CONTENT_DIR . '/cache/pages/';
if (!is_dir($cache_dir)) {
return;
}
$this->recursive_remove_directory($cache_dir);
wp_mkdir_p($cache_dir);
}
private function recursive_remove_directory($directory) {
foreach (glob($directory . '/*') as $file) {
if (is_dir($file)) {
$this->recursive_remove_directory($file);
} else {
unlink($file);
}
}
if (is_dir($directory)) {
rmdir($directory);
}
}
}
// Initialize cache invalidation
new Cache_Invalidation();
Best Practices
Caching Strategy Checklist
## Page Caching
- [ ] Cache HTML pages for anonymous users
- [ ] Exclude dynamic pages (cart, checkout, account)
- [ ] Set appropriate cache times (10min - 1hr for content)
- [ ] Implement cache warming for critical pages
- [ ] Monitor cache hit rates
## Object Caching
- [ ] Use Redis or Memcached for persistent object cache
- [ ] Cache expensive queries and API calls
- [ ] Set reasonable expiration times
- [ ] Monitor memory usage
- [ ] Implement cache groups for easy management
## Browser Caching
- [ ] Set far-future expires for static assets
- [ ] Use cache busting for updates
- [ ] Implement ETags for dynamic content
- [ ] Add appropriate Vary headers
- [ ] Test with different browsers
## CDN Caching
- [ ] Use CDN for all static assets
- [ ] Configure proper cache headers
- [ ] Implement cache purging API
- [ ] Monitor CDN hit rates
- [ ] Use multiple CDN regions
## Cache Invalidation
- [ ] Clear cache on content updates
- [ ] Implement smart invalidation (only affected pages)
- [ ] Avoid clearing entire cache when possible
- [ ] Set up automatic cache warming
- [ ] Monitor invalidation frequency
Conclusion
Effective caching is essential for WordPress performance, but it requires understanding multiple layers and implementing the right strategy for each. By following this guide:
- Page caching can reduce server load by 90%+ for anonymous visitors
- Object caching speeds up database queries by 10-100x
- Browser caching eliminates unnecessary requests entirely
- CDN caching reduces latency for global visitors
- Smart invalidation ensures fresh content without performance penalties
Remember that caching is not "set and forget"—it requires ongoing monitoring and optimization. Start with basic page caching, then progressively add layers based on your site's specific needs. The investment in proper caching pays enormous dividends in performance, user experience, and reduced hosting costs.
Continue optimizing your WordPress site with our comprehensive WordPress Performance Optimization Guide for more advanced techniques and strategies.