Initial impact: what the problem looks like from outside
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.
Investigation: ruling out the usual suspects
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.
The discovery: identical keys in one keyspace
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 statusREDISCLI_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:alloptionswp redis status on each site usually confirms it: the same database number and an empty or identical prefix across all of them.
Temporary mitigation: stop the bleeding without making it worse
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:
FLUSHDBclears the entire selected Redis database for every site using it, not just the broken one.FLUSHALLclears 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 architectural fix: give every site its own namespace
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 );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.