没有不值得去解决的问题,也没有不值得去学习的技术!

Fixing Cross-Host Cache Issues With Polylang and W3 Total Cache in WordPress

作者:

Recently, I migrated the WordPress English site from its original /en/ path to an independent subdomain. The Chinese site is currently https://www.shuijingwanwq.com, the English site is https://en.shuijingwanwq.com, and the WordPress admin is accessed via https://admin.shuijingwanwq.com.

Polylang continues to handle multilingual support; W3 Total Cache handles Page Cache; Object Cache is stored in Redis via W3 Total Cache; the Chinese site uses EdgeOne for its CDN, while the English site uses Cloudflare.

When the English site was still on the /en/ path, the Chinese and English versions shared the same www.shuijingwanwq.com Host despite having different URLs. After migrating to an independent English subdomain, the same WordPress installation began running simultaneously under three Hosts: www, en, and admin. This exposed cache invalidation issues that were previously not obvious.

This troubleshooting effort primarily addresses cache consistency at the Origin layer—namely, WordPress, Polylang, W3 Total Cache, and Redis. I ultimately identified two scenarios requiring compensation: when an English post is saved from the independent admin domain, the native W3 Total Cache workflow does not cover the English homepage Page Cache; additionally, the same type of Post object in the Redis Object Cache might exist in cache spaces corresponding to different Hosts, requiring a cross-Host invalidation mechanism.

This article does not cover EdgeOne and Cloudflare CDN cache invalidation. The CDN represents the next layer of issues and will be addressed separately later.


1. What Happened After Migrating from /en/ to an Independent Domain

Before the migration, the Chinese homepage was https://www.shuijingwanwq.com/, and the English content was located at https://www.shuijingwanwq.com/en/. Both languages resided under the same Host.

After the migration, the Chinese site became www.shuijingwanwq.com, the English site became en.shuijingwanwq.com, and the admin uses admin.shuijingwanwq.com. Polylang uses different domains to identify Chinese and English, while post publishing and updates occur on the independent admin Host.

This means that saving an English post simultaneously involves the WordPress save workflow under the admin Host, the post and homepage caches corresponding to the en Host, a portion of the URLs generated by W3 Total Cache defaults under the www Host, and the object caches corresponding to different Hosts in Redis.

Therefore, after the migration, it can no longer be simply understood as “the same site, just with an additional /en/ path.”


2. First, Identify Which Layer the Problem Is Actually On

The website currently has browser cache, CDN, Nginx, W3 Total Cache Page Cache, WordPress/Polylang, W3 Total Cache Object Cache, and Redis all running simultaneously. During troubleshooting, directly purging all caches or flushing Redis might restore the page, but it makes it difficult to determine which layer actually did the work.

Browser
↓
CDN
↓
Nginx
↓
W3 Total Cache Page Cache
↓
WordPress / Polylang
↓
W3 Total Cache Object Cache
↓
Redis

Therefore, I split the problem into two stages: the first stage focuses solely on confirming Origin cache consistency; the second stage will handle EdgeOne and Cloudflare CDN cache invalidation. This article documents the first stage.


3. The Native W3 Total Cache Post Save Workflow Does Not Fully Invalidate

After further inspecting the W3 Total Cache post save workflow, I confirmed that it is not entirely unaware of how to handle Polylang in a multi-domain environment.

For a specific post’s own URL, Polylang can still return the correct domain based on the post’s language. An English post permalink will resolve to en.shuijingwanwq.com, and a Chinese post permalink will resolve to www.shuijingwanwq.com, so the post’s own Page Cache is not the main issue.

The real problem occurs on pages like the homepage, which rely on the current request context to generate URLs. When a post is saved from admin.shuijingwanwq.com, the native W3 Total Cache workflow can handle the post’s own Page Cache and will also process targets like the default site’s www homepage, but it does not automatically add a cleanup for the https://en.shuijingwanwq.com/ homepage.

Thus, after saving an English post, the native workflow covers the English post’s own Page Cache and default site-related targets, but the English homepage Page Cache lacks corresponding cleanup. This is the first gap requiring compensation.


4. Redis Object Cache Has Another Layer of Host Isolation

Beyond Page Cache, W3 Total Cache Object Cache must also be considered. Upon further inspection of the current Redis cache keys, I found that specific object cache keys are affected by the current Host.

This means that when the same WordPress installation is loaded via www.shuijingwanwq.com, en.shuijingwanwq.com, and admin.shuijingwanwq.com, the same Post object might exist in cache spaces corresponding to different Hosts.

When updating a post in the admin, WordPress Core itself performs an exact clean_post_cache(). However, because this save request occurs on the admin Host, relying solely on the single object deletion in the current request is not enough to prove that old Post objects already existing under www and en will also be invalidated immediately.

In the current W3 Total Cache implementation, although specific object keys are isolated by Host, the generation/version used by the posts group is shared. Therefore, there is no need to iterate through www, en, and admin, simulate different HTTP_HOST, or flush the entire Redis.

The key judgments and group flush API retained in the public version are:

PHP
wp_cache_supports( 'flush_group' );
wp_cache_flush_group( 'posts' );

The actual logic first confirms that the current Object Cache supports group flush, then performs a limited invalidation on the posts group. This advances the shared generation/version, uniformly invalidating posts objects of the old generation across different Hosts.


5. The Final Solution Is Actually Quite Small

After identifying the two gaps, there was no need to reimplement a caching system. I only needed to add two actions:

  1. Determine the Polylang language based on the post itself, and obtain the homepage for that language;
  2. Precisely clean the W3 Total Cache Page Cache for that language’s homepage;
  3. Flush the posts Object Cache group when the Object Cache supports group flush;
  4. Actively execute the posts group flush only once within the same PHP request to avoid duplicate work.

The overall workflow can be simplified to:

Save English post in the admin dashboard
        ↓
W3 Total Cache native processing
        ├─ Clean the English post's own Page Cache
        └─ Process native targets like the www homepage

MU Plugin supplementary processing
        ├─ Clean the en English homepage Page Cache
        └─ Flush the posts Object Cache group
                    ↓
             www / en / admin
           Old posts objects uniformly invalidated

v0.1 only handles standard post where issues have been confirmed to exist. It does not include options, category, tag, taxonomy, menu, wp_template, wp_template_part, Global Styles, Theme JSON, or CDN cleanup.


6. Why Not Just Flush the Entire Redis

The simplest and most crude approach would naturally be to flush Redis and then clear all Page Caches as soon as a post is updated. This would likely allow you to see the latest content, but it is not suitable as a production solution.

  • A large number of objects unrelated to the current post would be invalidated simultaneously;
  • Subsequent requests would be more likely to集中 hit the origin database;
  • It could create additional CPU and database load spikes;
  • The benefits of Object Cache would also be diminished.

Therefore, I opted for a limited posts group invalidation instead of a global Object Cache flush.

PHP
wp_cache_flush_group( 'posts' );

I did not use wp_cache_flush(), nor did I directly execute Redis’s FLUSHDB. The goal was not “to clean the cache as thoroughly as possible,” but rather to invalidate only the objects confirmed to be potentially stale.


7. A Misjudgment Occurred During the First Production Verification

After completing the public MU Plugin, I conducted my first production verification. The English homepage Page Cache part went smoothly: after saving a post, the corresponding English homepage cache file no longer existed before initiating a new Origin GET, indicating that the English homepage Page Cache compensation logic executed successfully.

However, the Object Cache verification resulted in posts group version:812 → 812. It superficially appeared as though wp_cache_flush_group( 'posts' ) had not taken effect, so the public version was automatically rolled back to the original production plugin at that time.

This was later proven to be a false negative caused by the testing method, rather than a failure of the plugin itself.


8. The Real Problem Was the Testing Method, Not the Plugin

After continuing to compare the production version with the public version, I found no critical differences in the core Object Cache logic between the two codebases: both check wp_cache_supports( 'flush_group' ), both call wp_cache_flush_group( 'posts' ), and the Hook priority, argument count, standard post check, and in-request deduplication logic are also identical.

I eventually discovered the real issue: the first verification simulated saving a post via WP-CLI, and that context loaded the Core WP_Object_Cache fallback, not the W3 Total Cache Redis Object Cache drop-in used by real admin HTTP requests.

Therefore, the 812 → 812 in the WP-CLI test could not prove that the public plugin failed; it only showed that the current WP-CLI context is not suitable for verifying W3TC Redis group flush in real admin requests.

This conclusion was important for subsequent troubleshooting: at least in the current environment, wp post update cannot simply be treated as a fully equivalent verification to clicking “Update” in the WordPress admin.


9. Re-verifying with a Real Admin Update

After confirming the issue with the testing method, I redeployed public v0.1. Upon deployment completion, www Origin returned HTTP 200, en Origin returned HTTP 200, and admin returned a normal HTTP 302 login redirect.

I then established a pre-update baseline. The Redis posts group version was 813. The English homepage Page Cache file already existed, with an mtime of 2026-07-31 14:18:41.458486060 +0800, a size of 283002 bytes, and a SHA256 of 72e91f22a546dac51e928771e46cf1e85b26e4f9cb42e9004fa9e13cf80d2e33.

Next, I opened the English post 21022 from admin.shuijingwanwq.com, left the post content unmodified, and simply clicked “Update” once. This time, I did not use a WP-CLI simulation.


10. Real Admin Verification Results

The results this time were completely different from the first. After the real admin Update, the Redis posts group version advanced from 813 to 815, proving that the Object Cache group flush had actually occurred.

Meanwhile, after the Update completed and before the next Origin GET was initiated, the English homepage Page Cache file no longer existed, confirming that the precise cleanup of the English homepage Page Cache was again successful.

Upon subsequently visiting the English Origin for the first time, the homepage cache was regenerated. The new cache had an mtime of 2026-07-31 14:38:43.320046857 +0800, a size of 283011 bytes, and a SHA256 of 54d5b3bd10a0e2df3d1e71d499e394fcf412e828f5c5f53b305b8ca892a1e237.

After regeneration, both www and en Origin returned HTTP 200, and admin still returned a normal HTTP 302 login redirect. The first post on the English homepage was 21022, and the page HTML also contained 21022.

Thus, it can be confirmed: public v0.1 has passed verification via a real production WordPress admin Update and continues to run in the production environment.


11. What Changed at the Origin Layer After This Fix

For the currently verified standard English post publishing/updating scenario, upon saving, the post’s own Page Cache is invalidated; the native W3 Total Cache workflow continues to process default site-related targets; the MU Plugin supplementary cleans the English language homepage; simultaneously, the posts Object Cache group version advances, invalidating Post objects of the old generation under www, en, and admin.

When the next Origin request arrives, the page will be regenerated based on the latest objects. As a result, the Chinese and English homepages no longer need to wait for old Origin layer caches to naturally expire before displaying new posts.


12. Why the Browser Might Still See the Old Homepage After the Fix

After completing the Origin fix, I tested the Chinese and English homepages in a private browser window. The English homepage displayed the latest post 21022, but the Chinese homepage, when accessed directly, still did not show the latest post 21018.

Subsequently, I accessed the Chinese homepage with a new query parameter https://www.shuijingwanwq.com/?swq_home_test=20260731150601, and the page immediately displayed the latest post 21018.

Since it had already been confirmed that the Origin itself served the latest content, and changing the CDN cache key immediately retrieved the latest page, the remaining issue could be narrowed down to the old cache for the original Chinese homepage URL in EdgeOne CDN.

In other words, this fix resolved Origin cache consistency but did not automatically clean up the old HTML already cached by EdgeOne or Cloudflare. The CDN needs to be addressed separately in the next stage.


13. From “Potentially Stacking Two Layers of Staleness” to Mainly Just the CDN

Before the fix, if the Origin cache itself did not invalidate in a timely manner and the CDN continued to cache the old page returned by Origin, the staleness periods of the two layers could stack. Given the current CDN cache cycle of roughly 4 days, in extreme cases, the Origin old cache might persist for about 4 days, which, stacked with the CDN’s roughly 4 days, could result in a staleness window approaching 8 days.

This does not mean every post will necessarily be stale for 8 days; rather, when multiple cache layers rely on natural expiration, the staleness time can continue to stack.

Now, after saving a standard post, the old Origin Page Cache is actively invalidated, and the posts Object Cache group is advanced, so the Origin no longer contributes additional days of staleness. The primary remaining risk on the user side comes from the CDN. Based on the current cycle, the maximum staleness window can be compressed from the extreme case of two-layer stacking to mainly just the CDN’s roughly 4 days.

In the next stage, if we implement precise purging of the corresponding homepage caches in EdgeOne and Cloudflare after a post save, homepage updates on the user side will move closer to being “visible immediately after saving.”


14. Did Not Further Expand the Scope of v0.1

After completing this fix, I did not take the opportunity to add cache cleanup for categories, tags, menus, Theme JSON, and so on. The current v0.1 only addresses the two confirmed issues: precise invalidation of the corresponding language homepage Page Cache, and cross-Host invalidation of the posts Object Cache group.

If cache issues genuinely arise with categories, tags, menus, wp_template, Global Styles, or Theme JSON in the future, I will supplement the plugin based on actual evidence rather than prematurely turning it into a “flush all caches on post save” tool.

For this production environment, the more appropriate approach remains: when encountering a real problem, collect evidence first, confirm which cache layer it belongs to, make the minimal modification, and verify immediately.


15. Organizing the Fix into an Independent Open-Source MU Plugin

After passing production verification, I extracted this logic from the original site-specific MU Plugin and organized it into an independent project, wordpress-polylang-w3tc-cache-compat.

GitHub repository: https://github.com/shuijingwan/wordpress-polylang-w3tc-cache-compat.

The current version uses the MIT License. The scope of v0.1 is very clear: to supplement the confirmed standard post cache invalidation scenarios for environments with Polylang independent language domains + W3 Total Cache + Redis + an independent admin Host, rather than being a universal full-cache cleanup plugin.


16. Current Stage Conclusions

  1. After Polylang uses independent language domains, specific post URLs can still generate the correct domain based on the post language. However, when saving a post from the independent admin Host, the native W3 Total Cache homepage cleanup does not fully cover the English language homepage.
  2. In W3 Total Cache Redis Object Cache, specific objects are isolated by Host, but the shared posts group generation/version can be used for uniform cross-Host invalidation.
  3. To fix this issue, there is no need to flush the entire Redis, nor to simulate request object deletions separately for www, en, and admin.
  4. The WP-CLI Object Cache runtime environment cannot be assumed by default to be the real admin HTTP request environment; the initial 812 → 812 ultimately proved to be a false negative caused by the testing method.
  5. A real admin Update has verified posts group version:813 → 815, and the English homepage Page Cache is deleted immediately after a post update and regenerated on the next Origin request.

Therefore, it can now be confirmed: after a standard post update, the cache consistency issue for the Chinese and English homepages at the WordPress Origin layer has been resolved.

However, the entire cache chain is not yet finished. Actual browser testing has confirmed: the English homepage can display the latest post, while the Chinese homepage’s original URL might still return old content from EdgeOne; adding a new query parameter immediately displays the latest post.

So the goal for the next stage is very clear: handle precise cache invalidation at the EdgeOne and Cloudflare CDN layers. This part will continue to be troubleshooted separately and organized into a follow-up article.

系列导航

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理