Bali, Indonesia
Insights

Two WordPress Sites Sharing One Redis After Migration Started Showing Each Other's Data

October 29, 2026
0:00
Several WordPress sites get moved onto one new server, Redis is installed once, and object caching is switched on for each site. Soon after, one site starts showing another site's menu, its title changes to a different brand, or it redirects visitors to a completely different domain. Nothing was hacked. The sites are reading each other's cache because they all write into the same Redis keyspace using the same key names. This write-up walks through that failure the way a postmortem would: what users see, how the investigation usually goes, the point where the cause becomes obvious, what to do right away, and how to set things up so it cannot happen again.
The symptoms rarely look like a caching problem at first. Typical reports include a site that suddenly uses another site's name in the browser tab, navigation menus or widgets that belong to a different install, a homepage that redirects to another domain on the same server, or a logged-in user who sees odd behavior in the admin bar or gets bounced out of wp-admin. The redirect case is the most alarming, and it has a simple explanation. WordPress keeps its autoloaded options, including siteurl and home, in one cached entry. If site B loads site A's copy of that entry, site B believes its own URL is site A's domain and redirects there. The problem is also intermittent. Whichever site wrote to the shared key most recently wins, so the wrong data appears, disappears after an expiry or a save, then comes back. That inconsistency is what makes people suspect DNS, the browser, or a CDN before they look at Redis.
The first round of checks is the standard one after any migration. DNS points to the right server. The web server virtual host maps each domain to the correct document root. Each site's wp-config.php points to its own database. All of that checks out. The next useful check is the database itself. Query the wp_options table (or whatever table prefix the site uses) for siteurl and home. The values in MySQL are correct. So the stored data is right, but the value WordPress uses at runtime is wrong. That gap points to something sitting between WordPress and the database, and on a server with Redis enabled, that something is the persistent object cache. The quickest confirmation is to temporarily disable the object cache on one affected site. If the wrong data disappears immediately and the site behaves normally on plain database reads, the cache is the source.
WordPress has an internal object cache that stores things like options, posts, terms, and user data in named groups, such as options, posts, terms, and users. By default that cache lives only for one request. A drop-in file, wp-content/object-cache.php, replaces it with a persistent backend like Redis, and it does so by turning each group and key into a Redis key name. If you are not sure the drop-in is actually in place on a site, this article on Redis being active without object-cache.php covers that check. Here is the part that causes the collision. The key name is built from things like the group, the item key, and sometimes the table prefix. Redis itself has no idea which site a key belongs to. If two sites use the same table prefix (very common when sites are cloned from one template or installed with defaults) and neither sets a unique cache prefix, their key for autoloaded options ends up with exactly the same name. Same for menu terms, user ID 1's cached data, and many transients stored in the cache. Two sites, one key, last writer wins. User data is why logged-in state can look wrong. Cached user records and user meta, which includes session tokens, live in the cache too. If two sites both have a user with ID 1, they can end up reading the same cached entry. Browser cookies are still per domain, but the server-side data those cookies are checked against may come from the wrong site. You can confirm the collision directly with redis-cli. These commands are read-only. --scan iterates keys gradually, unlike KEYS *, which can block a busy Redis instance.
BASH
# Show which logical databases contain keys, and how many
redis-cli INFO keyspace

# Find autoloaded-options keys in database 0 without blocking Redis
redis-cli -n 0 --scan --pattern '*alloptions*'

# From each site's document root: what the drop-in reports
# (requires the Redis Object Cache plugin's WP-CLI commands)
wp redis status
If Redis requires a password, set it through the REDISCLI_AUTH environment variable rather than typing it on the command line. If Redis listens on a Unix socket, add -s with the socket path. The tell is the count. With five sites sharing one database, you expect five distinct alloptions keys, each with a different prefix. A collision looks like this instead:
TEXT
Illustrative output format:

$ redis-cli -n 0 --scan --pattern '*alloptions*'
wp_:options:alloptions
One key, shared by everyone. wp redis status on each site usually confirms it: the same database number and an empty or identical prefix across all of them.
The immediate goal is to stop sites from reading each other's data. There are two reasonable options. The safest short-term move is to disable the object cache on all but one of the colliding sites (wp redis disable removes the drop-in for the Redis Object Cache plugin). Those sites fall back to database reads. They get slower under load, but they serve correct data. The second option is to set unique prefixes right away (next section) and then clear the poisoned keys. Be careful here:
  • FLUSHDB clears the entire selected Redis database for every site using it, not just the broken one.
  • FLUSHALL clears every database on the instance, including anything unrelated to WordPress, such as PHP sessions or job queues stored in Redis.
  • Both are irreversible. For a pure object cache the data rebuilds from MySQL, but every site loses its warm cache at once, which can cause a load spike.
The plugin's own "flush cache" button may also flush the whole database rather than just one site's keys, depending on version and settings, so check its documentation before relying on it. One more step people miss: purge the page cache. If a full-page cache stored HTML that was rendered with the wrong options or menu, visitors keep seeing it even after Redis is fixed.
The fix is to make every site's keys unique before they reach Redis. Most Redis drop-ins support a prefix constant in wp-config.php, and many also accept WP_CACHE_KEY_SALT, which is the more generic name used across several object cache implementations. Add the constants above the "That's all, stop editing!" line so they are defined before the drop-in loads.
PHP
// wp-config.php, above the 'That's all, stop editing!' line
// Must be unique per site. Never copy this value between installs.
define( 'WP_REDIS_PREFIX', 'shop-example-com:' );

// Read by some drop-ins instead of, or as a fallback for, WP_REDIS_PREFIX
define( 'WP_CACHE_KEY_SALT', 'shop-example-com:' );

// Optional: a separate logical Redis database for this site
define( 'WP_REDIS_DATABASE', 2 );
A prefix based on the site's domain is easy to read in redis-cli and hard to duplicate by accident. After setting it, run wp redis status again and repeat the --scan check. You should see one alloptions key per site, each carrying its own prefix. Prefix and database index solve the same problem in different ways, and the trade-offs are worth knowing. A logical database (WP_REDIS_DATABASE) keeps keyspaces fully separate, and flushing one database leaves the others alone. But there are only a limited number of them (16 by default), and Redis Cluster supports only database 0. A prefix works everywhere and scales to any number of sites, but a FLUSHDB still hits everyone in that database. A common rule of thumb is to always set a unique prefix, and add a separate database index when you want flushes to stay contained. Neither one is a security boundary. Any client that can connect to Redis can switch databases and read any key. If the sites belong to different owners, or one compromised site should not be able to read another's cached user data, use separate Redis instances or Redis ACL users restricted to their own key pattern, where your Redis version and drop-in support it. Finally, deal with the leftovers. The old unprefixed keys stay in Redis until they expire or get evicted. Whether that happens on its own depends on your maxmemory and maxmemory-policy settings. Once every site has its own prefix, a single planned flush during a quiet period clears them out cleanly. The practical lesson for migrations is simple. When several WordPress sites land on one server with one Redis, treat a unique cache prefix as part of provisioning, set in wp-config.php before object caching is enabled, not something to add after sites start showing each other's data. When a customer reports that their site shows another site's name or redirects to someone else's domain, compare the database value with what the site serves. If they differ and Redis is in use, scan for duplicate keys before touching anything else.