Managing WordPress Object Cache with Memcached: Per-Site Flush, Monitoring & Optimization


We run multiple WordPress sites on a single VPS, all using Memcached for object caching. The problem? Flushing the object cache for one site risks wiping the cache for all others sharing the same memory pool.
In this post, Iโll walk you through:
Why object caching is critical for WordPress performance
How Memcached behaves in shared VPS environments
And how we built a custom plugin to safely flush cache per site using
WP_CACHE_KEY_SALT
If youโre managing multiple sites, I hope this guide helps you optimize caching with clarity and confidence.
1. Why Object Caching Matters in WordPress
Object caching temporarily stores the results of complex database queries, WordPress options, or transients in memory so they can be quickly reused. This avoids hitting the MySQL database unnecessarily and significantly reduces load time.
Why it matters:
Faster page loads (especially for dynamic or logged-in requests)
Reduced database stress under traffic spikes
Essential for scaling WordPress on high-traffic sites
Memcached vs Redis for WordPress
Feature | Memcached | Redis |
Data structure | Key-value only | Supports advanced types (lists, sets, etc.) |
Persistence | In-memory only (no persistence) | Optional persistence to disk |
Use case | Lightweight, fast for object caching | More flexible, often used in Laravel or apps needing queues |
WordPress fit | Great for object cache (transients, queries) | Also great; some plugins prefer Redis |
In many cases, Memcached is faster and simpler to configure, and itโs widely supported by LiteSpeed and cPanel providers.
2. How Memcached Works in Shared VPS Environments
Default Port 11211 and Shared Instances
Memcached by default runs on port 11211. Unless explicitly isolated per app, all websites on the server connect to the same instance. That means:
A flush command (
flush_all
) affects all keys from all sites.Thereโs no native separation of site-specific data.
Why You Need to Namespace Your Keys
WordPress supports namespacing via:
define('WP_CACHE_KEY_SALT', 'yoursiteprefix_');
This is essential. It prepends a unique string to every cache key generated by WordPress, allowing plugins or scripts (like ours) to selectively flush only your siteโs cache.
Without it, you canโt safely delete keys without affecting others.
Memory Limits: Default vs Optimized
Default Memcached allocation on many cPanel servers is 64 MB.
You can monitor it using tools like:
getStats()
via scriptWordPress Object Cache Pro
Custom scripts with
fsockopen
or Telnet
Example output:
Memory Used : 37 MB
Memory Limit : 64 MB
Evictions : 1.4M (means old data is being overwritten)
Hit Rate : 94%
What Happens When Multiple Sites Share the Same Instance
Cache collisions (without
WP_CACHE_KEY_SALT
)Overwrites and evictions
Full flushes affect every site
Monitoring gets confusing unless you prefix keys and track them separately
Thatโs why we decided to build our own flusher plugin tailored to a multi-site VPS scenario.
3. Building a Custom Cache Flusher Plugin
๐ก The Problem
WordPress provides a built-in function:
wp_cache_flush();
But thereโs a catch:
It only works via WP-CLI.
If used programmatically, it often gets blocked in
object-cache.php
or flushes everything โ not safe for shared environments.
๐จ Plugin Features
Adds a โFlush Object Cacheโ option under Tools โ Flush Object Cache
Detects cache backend: Memcached, Redis, APC, or unknown
Checks for
WP_CACHE_KEY_SALT
If defined โ flushes only matching keys
๐งช Technical Highlights
Uses
Memcached::getAllKeys()
when availableUses
delete()
for each key that starts with the defined saltHandles extensions that donโt support key enumeration (e.g., fallback message)
Displays real-time status messages like:
โ Flushed 318 keys using
WP_CACHE_KEY_SALT
โ ๏ธ Salt not defined and no confirmation to flush all cache
โ Backend not detected
4. Monitoring Memcached Usage
Once object caching is active, blindly assuming itโs helping is a mistake. You need visibility into how your Memcached instance is performing, especially if itโs shared among multiple sites.
We built a lightweight PHP script that outputs useful Memcached stats:
<?php
/**
* Memcached Monitor โ WordPress-safe PHP script
* Shows or logs Memcached usage: items, memory, hits, evictions
*/
header('Content-Type: text/plain');
function socf_get_memcached_stats() {
$sock = @fsockopen('127.0.0.1', 11211);
if (!$sock) {
return "โ Could not connect to Memcached at 127.0.0.1:11211.";
}
fwrite($sock, "stats\n");
$stats = [];
while (!feof($sock)) {
$line = fgets($sock, 128);
if (strpos($line, 'STAT') === 0) {
$parts = explode(' ', trim($line));
$stats[$parts[1]] = $parts[2];
} elseif (trim($line) === 'END') {
break;
}
}
fclose($sock);
// Output
$output = "โ
Memcached Status:\n";
$output .= "-------------------\n";
$output .= "Items Stored : " . number_format($stats['curr_items']) . "\n";
$output .= "Memory Used : " . round($stats['bytes'] / 1024 / 1024, 2) . " MB\n";
$output .= "Memory Limit : " . round($stats['limit_maxbytes'] / 1024 / 1024, 2) . " MB\n";
$output .= "Cache Hits : " . number_format($stats['get_hits']) . "\n";
$output .= "Cache Misses : " . number_format($stats['get_misses']) . "\n";
$output .= "Evictions : " . number_format($stats['evictions']) . "\n";
$output .= "Hit Rate : " . (
($stats['get_hits'] + $stats['get_misses']) > 0
? round($stats['get_hits'] / ($stats['get_hits'] + $stats['get_misses']) * 100, 2)
: 0
) . "%\n";
return $output;
}
// Show or log depending on context
echo socf_get_memcached_stats();
You can run this script from your browser or cron to monitor performance.
โ
Memcached Status:
----------------------
Items Stored : 107,705
Memory Used : 37.36 MB
Memory Limit : 64 MB
Cache Hits : 631,841,892
Cache Misses : 35,675,800
Evictions : 1,476,500
Hit Rate : 94.66%
What These Metrics Mean
Memory Used vs Limit: If usage is consistently close to the limit (e.g., 60 MB of 64 MB), eviction is likely.
Hit/Miss Ratio: A high hit rate (90%+) means the cache is effective.
Evictions: This shows how many old entries Memcached had to delete to make space. Frequent evictions = not enough memory.
Items Stored: Number of keys in cache.
Tracking Per-Site Usage by Prefix (Salt)
We extended our script to group keys by WP_CACHE_KEY_SALT
prefix and output something like:
<?php
/**
* Memcached Site-wise Monitor (with defined salts)
* Groups keys by exact WP_CACHE_KEY_SALT values.
*/
header('Content-Type: text/plain');
// Define your known salts (must match what's in wp-config.php)
$salt_prefixes = [
'webdevstory_' => 'WebDevStory',
'site2_' => 'Site 2',
'site3_' => 'Site 3',
'site4_' => 'Site 4',
];
function socf_get_sitewise_memcached_keys($salt_prefixes) {
$sock = @fsockopen('127.0.0.1', 11211);
if (!$sock) {
return "โ Could not connect to Memcached at 127.0.0.1:11211.";
}
fwrite($sock, "stats items\r\n");
$slabs = [];
while (!feof($sock)) {
$line = fgets($sock, 128);
if (preg_match('/STAT items:(\d+):number/', $line, $matches)) {
$slabs[] = $matches[1];
} elseif (trim($line) === 'END') {
break;
}
}
$site_counts = array_fill_keys(array_values($salt_prefixes), 0);
$site_counts['Untracked'] = 0;
foreach (array_unique($slabs) as $slab) {
fwrite($sock, "stats cachedump $slab 200\r\n");
while (!feof($sock)) {
$line = fgets($sock, 512);
if (preg_match('/ITEM ([^\s]+) /', $line, $matches)) {
$key = $matches[1];
$matched = false;
foreach ($salt_prefixes as $salt => $label) {
if (strpos($key, $salt) === 0) {
$site_counts[$label]++;
$matched = true;
break;
}
}
if (!$matched) {
$site_counts['Untracked']++;
}
} elseif (trim($line) === 'END') {
break;
}
}
}
fclose($sock);
$output = "๐ Memcached Key Usage by Site (Salt Matching):\n";
$output .= "---------------------------------------------\n";
foreach ($site_counts as $label => $count) {
$output .= sprintf("๐น %-20s : %d keys\n", $label, $count);
}
return $output ?: "No keys found.";
}
echo socf_get_sitewise_memcached_keys($salt_prefixes);
๐ Memcached Key Usage by Site (Salt Matching):
-----------------------------------------------
Site 1 : 0 keys
Site 2 : 429 keys
Site 3 : 164 keys
Site 4 : 1273 keys
Untracked : 7 keys
This is invaluable when diagnosing:
Why a specific site is bloating memory
Whether your salt is missing (0 keys = wrong or undefined salt)
Sites not benefiting from caching at all
When and Why Evictions Happen
Evictions occur when Memcached runs out of memory and starts removing old keys (often the least recently used). Common causes:
Multiple high-traffic sites on the same Memcached instance
Large WooCommerce or multilingual setups
Default memory limit too small (e.g., 64 MB)
๐ก We upgraded our instance to 512 MB after observing high eviction counts and were able to reduce them significantly.
5. Deciding Which Sites Should Use Object Cache
Not all websites need object caching, or at least not Memcached/Redis-level caching.
โ When Object Cache Is Helpful
WooCommerce stores: Dynamic product and cart pages, session data, etc.
Multilingual websites: Lots of options and translation strings cached
Membership or login-based sites: Auth checks and custom queries
Content-heavy blogs with logged-in users (e.g., WebDevStory)
In these cases, object caching significantly reduces query load and boosts TTFB.
โ When Object Cache May Not Be Worth It
Small static brochure sites
Low-traffic blogs with infrequent updates
Minimal plugin usage
For example, we disabled Memcached for a site which is:
Static and updated rarely
Lightweight in theme and plugins
Visited occasionally, mostly by anonymous users
Using object cache here would just consume space in shared memory and increase complexity.
๐ก Rule of Thumb
Scenario | Use Object Cache? |
WooCommerce with 500+ products | โ Yes |
Blog with 50 posts, no login | โ Probably not |
Multilingual portfolio site | โ Yes |
Static info page | โ Skip it |
Membership site | โ Absolutely |
6. Best Practices for Multi-site Memcached Use
โ Always Define WP_CACHE_KEY_SALT
This is critical. Without it:
All keys go into a global pool
You lose the ability to flush per site
Monitoring tools canโt differentiate usage
Sample setup per wp-config.php:
define('WP_CACHE_KEY_SALT', 'webdevstory_');
๐ซ Avoid Full Flush Unless Using Dedicated Memcached
Unless youโre on a single-site server, never flush the entire cache without confirming
Use a plugin (like ours) that prevents accidental full flush without salt
Full flushes can cause downtime or performance drops across other sites on the same instance
๐ Use Monitoring to Balance Memory
Whether through:
A custom PHP stats script
LiteSpeed cache panel
Query Monitor plugin (advanced)
Regular monitoring helps answer:
Should you increase memory?
Is one site bloating the cache?
Are your hit rates improving?
๐ง Be Aware of Key Growth and Expiry
Memcached stores keys in memory until:
They expire (default: 0 = never)
Theyโre evicted due to space pressure
If your plugin or theme stores too many transients or uses long TTLs (wp_cache_set( 'key', 'value', 'group', 3600 ))
, you may:
Waste memory on stale data
Trigger unnecessary evictions
Hurt performance rather than improve it
๐ Set sensible expiration times and review whatโs being cached if you suspect bloat.
Final Thoughts
Object caching can supercharge your WordPress site โ but only if managed correctly.
From small blogs to large WooCommerce stores, the difference between efficient and wasteful caching comes down to:
Using Memcached wisely in shared or VPS setups
Defining a proper
WP_CACHE_KEY_SALT
for isolationMonitoring usage and tuning memory limits
Having tools to flush intelligently, not blindly
We learned the hard way: full cache flushes, shared memory conflicts, and missed key prefixes can cost you performance and stability.
Try Our Simple Object Cache Flusher Plugin
To help manage this, we built a lightweight plugin with:
โ Admin button to flush object cache
โ Selective flush by salt (
WP_CACHE_KEY_SALT
)โ Full-flush confirmation if no salt is defined
โ Memcached backend detection
โ Safe UI feedback with hit/miss logging options
<?php
/**
* Plugin Name: Simple Object Cache Flusher
* Description: Adds an admin menu with backend info and a button to flush only this site's object cache (Memcached) using WP_CACHE_KEY_SALT.
* Version: 1.3
* Author: Mainul Hasan
* Author URI: https://www.webdevstory.com/
* License: GPL2+
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: simple-object-cache-flusher
* Domain Path: /languages
*/
add_action('admin_menu', function () {
add_management_page(
__('Flush Object Cache', 'simple-object-cache-flusher'),
__('Flush Object Cache', 'simple-object-cache-flusher'),
'manage_options',
'flush-object-cache',
'socf_admin_page'
);
});
function socf_admin_page() {
if (!current_user_can('manage_options')) {
wp_die(__('Not allowed.', 'simple-object-cache-flusher'));
}
$cache_backend = __('Unknown', 'simple-object-cache-flusher');
if (file_exists(WP_CONTENT_DIR . '/object-cache.php')) {
$file = file_get_contents(WP_CONTENT_DIR . '/object-cache.php');
if (stripos($file, 'memcached') !== false) {
$cache_backend = 'Memcached';
} elseif (stripos($file, 'redis') !== false) {
$cache_backend = 'Redis';
} elseif (stripos($file, 'APC') !== false) {
$cache_backend = 'APC';
} else {
$cache_backend = __('Custom/Other', 'simple-object-cache-flusher');
}
} else {
$cache_backend = __('Not detected / Disabled', 'simple-object-cache-flusher');
}
if (isset($_POST['socf_flush'])) {
check_admin_referer('socf_flush_cache', 'socf_nonce');
$prefix = defined('WP_CACHE_KEY_SALT') ? WP_CACHE_KEY_SALT : '';
$deleted = 0;
$error_msg = '';
if ($prefix && class_exists('Memcached')) {
$host = apply_filters('socf_memcached_host', '127.0.0.1');
$port = apply_filters('socf_memcached_port', 11211);
$mem = new Memcached();
$mem->addServer($host, $port);
if (method_exists($mem, 'getAllKeys')) {
$all_keys = $mem->getAllKeys();
if (is_array($all_keys)) {
foreach ($all_keys as $key) {
if (strpos($key, $prefix) === 0) {
if ($mem->delete($key)) {
$deleted++;
}
}
}
}
} else {
$error_msg = 'Your Memcached extension does not support key enumeration (getAllKeys). Partial flush not possible.';
}
}
if ($deleted > 0) {
echo '<div class="notice notice-success is-dismissible"><p>' .
esc_html__('โ
Flushed ' . $deleted . ' object cache keys using WP_CACHE_KEY_SALT.', 'simple-object-cache-flusher') .
'</p></div>';
} else {
echo '<div class="notice notice-warning is-dismissible"><p>' .
esc_html__('โ ๏ธ No matching keys deleted. Either WP_CACHE_KEY_SALT is not set, or key listing is unsupported. ', 'simple-object-cache-flusher') .
esc_html($error_msg) .
'</p></div>';
}
}
?>
<div class="wrap">
<h1><?php esc_html_e('Flush Object Cache', 'simple-object-cache-flusher'); ?></h1>
<p><strong><?php esc_html_e('Backend detected:', 'simple-object-cache-flusher'); ?></strong> <?php echo esc_html($cache_backend); ?></p>
<form method="post">
<?php wp_nonce_field('socf_flush_cache', 'socf_nonce'); ?>
<p>
<input type="submit" name="socf_flush" class="button button-primary"
value="<?php esc_attr_e('Flush Object Cache Now', 'simple-object-cache-flusher'); ?>"/>
</p>
</form>
<p><?php esc_html_e("This will flush the Memcached object cache if available on your server. Tries to delete only this site's keys if salt is defined.", 'simple-object-cache-flusher'); ?></p>
<?php if ($cache_backend === __('Not detected / Disabled', 'simple-object-cache-flusher')) : ?>
<p style="color: red;"><?php esc_html_e('No object cache backend detected. You may not be using object caching.', 'simple-object-cache-flusher'); ?></p>
<?php endif; ?>
</div>
<?php
}
add_action('plugins_loaded', function () {
load_plugin_textdomain('simple-object-cache-flusher', false, dirname(plugin_basename(__FILE__)) . '/languages');
});
๐ Check it out on GitHub
๐ Before You Go:
๐ Found this guide helpful? Give it a like!
๐ฌ Got thoughts? Share your insights!
๐ค Know someone who needs this? Share the post!
๐ Your support keeps us going!
๐ป Level up with the latest tech trends, tutorials, and tips - Straight to your inbox โ no fluff, just value!
Subscribe to my newsletter
Read articles from Mainul Hasan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Mainul Hasan
Mainul Hasan
Hello, I'm Mainul Hasan. Currently, Iโm working as a Teaching Assistant at the University of Oslo and pursuing my Masterโs program in Informatics: Programming and System Architecture. Before this, I worked as a Web Developer in several companies, gaining experience in different tech stacks and programming languages such as JavaScript, PHP, and Python. Inspired by my professional journey and desire for lifelong learning, I've started to write about Tech & Lifestyle, Web Development, and the Digital Nomad Life. Here, I share my knowledge and experiences, engage in discussions with my readers, and strive to make my spare time more meaningful. Feel free to connect with me on LinkedIn or email me at hi@mmainulhasan.com for any potential opportunities. Happy Learning!