Reduce WordPress Time to First Byte with Object Caching and Redis

WordPress Time to First Byte (TTFB) is dominated by PHP execution time and database query time — on an uncached page, WordPress executes dozens of MySQL queries to assemble posts, metadata, menus, widgets, and options, then runs PHP to render HTML. Object caching with Redis stores the results of these queries in memory and serves subsequent requests from cache without touching MySQL, reducing TTFB from 500–2000 ms to 20–80 ms for cached pages. WordPress includes a built-in object cache API (wp_cache_get(), wp_cache_set(), wp_cache_delete()) that stores data in PHP memory by default — data persists only for the current request. Replacing the default handler with a persistent object cache drop-in (wp-content/object-cache.php) connects the API to Redis, making cache data persist across requests and processes. The most widely used drop-ins are the Predis-based WordPress Object Cache plugin by Till Krüss and the Relay extension by Beyond Code — Relay is a PHP extension that keeps the Redis connection pool and serialized data in shared memory between PHP-FPM workers, eliminating per-request TCP round-trips to Redis. Cache groups in WordPress (the second argument to wp_cache_get()) allow targeted invalidation: the posts group is flushed on post save, the terms group on term update, and the options group on update_option() — these automatic invalidations are built into WordPress core and do not require additional code. The WP_CACHE constant in wp-config.php must be true for the object cache drop-in to activate — without it, object-cache.php is ignored. Redis connection configuration (host, port, database, password, timeout) is passed to the drop-in via WP_REDIS_HOST, WP_REDIS_PORT, and WP_REDIS_DATABASE constants in wp-config.php. Page caching (serving static HTML files without executing PHP) is a complementary layer that reduces TTFB to near-zero for anonymous visitors — object caching benefits logged-in users and admin pages where page caching is disabled. The SWR caching post shows transient-based application-level caching — object caching and SWR operate at different layers and complement each other.

Problem: WordPress TTFB exceeds 500 ms on uncached requests because every page load executes 30–100 MySQL queries for options, menus, and post meta — a traffic spike multiplies this load linearly and can saturate the database connection pool.

Solution: Install Redis, deploy a persistent object cache drop-in to wp-content/object-cache.php, set WP_CACHE = true and Redis connection constants in wp-config.php, and verify cache hit rates with redis-cli info stats to confirm the setup is working.

# Install Redis on Ubuntu 22.04
sudo apt update && sudo apt install -y redis-server

# Set Redis to listen only on localhost and set a password
sudo sed -i 's/^# requirepass.*/requirepass YOUR_STRONG_PASSWORD/' /etc/redis/redis.conf
sudo sed -i 's/^bind .*/bind 127.0.0.1/' /etc/redis/redis.conf
sudo systemctl restart redis

# Install the WordPress Redis object cache drop-in via WP-CLI
wp plugin install redis-cache --activate
wp redis enable

# Verify Redis is receiving cache operations
redis-cli -a YOUR_STRONG_PASSWORD info stats | grep -E 'keyspace_hits|keyspace_misses|instantaneous_ops_per_sec'

// wp-config.php — Redis object cache configuration

define('WP_CACHE', true); // required to activate object-cache.php drop-in

define('WP_REDIS_HOST',     '127.0.0.1');
define('WP_REDIS_PORT',     6379);
define('WP_REDIS_PASSWORD', 'YOUR_STRONG_PASSWORD');
define('WP_REDIS_DATABASE', 0);          // Redis DB index (0-15)
define('WP_REDIS_TIMEOUT',  1);           // connection timeout in seconds
define('WP_REDIS_READ_TIMEOUT', 1);
define('WP_REDIS_PREFIX',   'mysite_');   // prefix to isolate keys on shared Redis

// Optional: selectively disable cache for admin requests to avoid stale data
// define('WP_REDIS_DISABLED', is_admin());

// Using the object cache API in custom code
function get_expensive_report(): array {
    $cache_key   = 'monthly_revenue';
    $cache_group = 'my_reports';
    $data = wp_cache_get($cache_key, $cache_group);

    if (false === $data) {
        // Run the expensive query
        $data = run_revenue_query_from_db();
        wp_cache_set($cache_key, $data, $cache_group, 15 * MINUTE_IN_SECONDS);
    }

    return $data;
}

// Invalidate a cache group key manually
wp_cache_delete('monthly_revenue', 'my_reports');

// Flush the entire Redis database (use with care — flushes all WordPress caches)
wp_cache_flush();

NOTE: Redis FLUSHDB (called by wp_cache_flush()) clears all keys in the selected database — if multiple WordPress sites share the same Redis instance and database index, a cache flush on one site flushes all of them. Use the WP_REDIS_PREFIX constant to namespace keys and set each site to a different database index (WP_REDIS_DATABASE) to provide isolation without running separate Redis instances.