Building a Blogging Site with React and PHP: A Step-by-Step Guide
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:
WP_CACHE_KEY_SALTIf you’re managing multiple sites, I hope this guide helps you optimize caching with clarity and confidence.
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.
| 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.
Memcached by default runs on port 11211. Unless explicitly isolated per app, all websites on the server connect to the same instance. That means:
flush_all) affects all keys from all sites.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.
Default Memcached allocation on many cPanel servers is 64 MB.
You can monitor it using tools like:
getStats() via scriptfsockopen or Telnet
Memory Used : 37 MB
Memory Limit : 64 MB
Evictions : 1.4M (means old data is being overwritten)
Hit Rate : 94%
WP_CACHE_KEY_SALT)That’s why we decided to build our own flusher plugin tailored to a multi-site VPS scenario.
WordPress provides a built-in function:
wp_cache_flush();
But there’s a catch:
object-cache.php or flushes everything — not safe for shared environments.WP_CACHE_KEY_SALTMemcached::getAllKeys() when availabledelete() for each key that starts with the defined saltWP_CACHE_KEY_SALTOnce 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, "statsn");
$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) . " MBn";
$output .= "Memory Limit : " . round($stats['limit_maxbytes'] / 1024 / 1024, 2) . " MBn";
$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%
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 itemsrn");
$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 200rn");
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 keysn", $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:
Evictions occur when Memcached runs out of memory and starts removing old keys (often the least recently used). Common causes:
💡 We upgraded our instance to 512 MB after observing high eviction counts and were able to reduce them significantly.
Not all websites need object caching — or at least not Memcached/Redis-level caching.
In these cases, object caching significantly reduces query load and boosts TTFB.
For example, we disabled Memcached for a site which is:
Using object cache here would just consume space in shared memory and increase complexity.
| 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 |
This is critical. Without it:
Sample setup per wp-config.php:
define('WP_CACHE_KEY_SALT', 'webdevstory_');
Whether through:
Regular monitoring helps answer:
Memcached stores keys in memory until:
If your plugin or theme stores too many transients or uses long TTLs (wp_cache_set( 'key', 'value', 'group', 3600 )), you may:
📌 Set sensible expiration times and review what’s being cached if you suspect bloat.
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:
WP_CACHE_KEY_SALT for isolationWe learned the hard way: full cache flushes, shared memory conflicts, and missed key prefixes can cost you performance and stability.
To help manage this, we built a lightweight plugin with:
WP_CACHE_KEY_SALT)
<?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><p>' .
esc_html__('✅ Flushed ' . $deleted . ' object cache keys using WP_CACHE_KEY_SALT.', 'simple-object-cache-flusher') .
'</p></div>';
} else {
echo '<div><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>
<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"
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
💻 Level up with the latest tech trends, tutorials, and tips - Straight to your inbox – no fluff, just value!
Note: Some links on this page might be affiliate links. If you make a purchase through these links, I may earn a small commission at no extra cost to you. Thanks for your support!
Leave a Reply
Your email address will not be published. Required fields are marked *