Previously, I performed a Query String normalization process for WordPress article detail pages.
When an article detail page includes amp, nonamp, random test parameters, or other meaningless query parameters, the CDN no longer builds a separate cache for each parameter combination. Instead, it uniformly reuses the cache corresponding to the canonical URL. At the same time, when there is a cache miss and a request is sent to the origin, the query parameters are no longer passed to WordPress.
The previous process is documented in this article:
Unifying WordPress Article Query Strings via EdgeOne and Cloudflare
However, a subsequent EdgeOne 524 incident made me realize that processing only article detail pages was insufficient.
A large number of category pages, tag pages, series pages, date archive pages, and paginated URLs are also appended with various query parameters by crawlers. If all these parameters are included in the CDN cache key, they will create massive cache fragmentation and repeatedly trigger dynamic origin requests to WordPress.
This time, I extended the same Query String normalization scheme to WordPress public list pages, completing the configuration and verification on the Chinese site’s EdgeOne and the English site’s Cloudflare, respectively.
1. How the Problem Surfaced
On the evening of August 2, 2026, the Chinese site experienced a surge of EdgeOne 524 errors.
During the incident window, EdgeOne recorded 151 524 errors:
- Cache MISS: 147
- Dynamic requests: 4
- Cache HIT: 0
These requests were not concentrated on a single article but were scattered across the homepage, category pages, tag pages, date archives, and paginated URLs.
The origin Nginx logs simultaneously showed a large number of 499 errors. The requests included the homepage, category pages, and URLs with parameters such as query-62-page, amp, and nonamp.
PHP-FPM was using the following configuration at the time:
- ECS: 1-core CPU
pm = ondemandpm.max_children = 7request_slowlog_timeout = 3srequest_terminate_timeout = 600s
The slow log showed that during the incident, 7 PHP-FPM child processes were simultaneously stuck in index.php. The log then showed:
server reached pm.max_children setting (7)
Since the server only has a 1-core CPU, continuing to increase pm.max_children was not a reasonable direction.
Even increasing the number of PHP processes would only cause more processes to contend for the same CPU. What truly needed to be reduced was:
The same page being repeatedly treated as different resources due to different Query Strings, causing CDN cache misses and simultaneous origin requests executing WordPress.
2. Why Query Parameters Amplify Origin Load
The following URLs might actually return the exact same content in WordPress:
https://www.shuijingwanwq.com/tag/cache/
https://www.shuijingwanwq.com/tag/cache/?amp=1
https://www.shuijingwanwq.com/tag/cache/?nonamp=1
https://www.shuijingwanwq.com/tag/cache/?query-62-page=43
https://www.shuijingwanwq.com/tag/cache/?random=value
However, if the CDN includes the full Query String in the cache key by default, they will be treated as multiple distinct cache objects.
Each time a new parameter combination appears for the first time, it can trigger an origin request.
When crawlers simultaneously access a large number of category, tag, paginated, and archive pages, this cache fragmentation can quickly exhaust all PHP-FPM processes.
Therefore, this optimization adopted a two-layer approach:
- Query Strings do not participate in the CDN cache key;
- On a cache miss during origin fetch, the Query String is directly removed.
The first layer prevents cache fragmentation, while the second prevents meaningless parameters from being passed to WordPress.
3. Which Public List Pages Need Coverage
I first compiled the list pages currently existing on the Chinese site:
https://www.shuijingwanwq.com/
https://www.shuijingwanwq.com/page/4/
https://www.shuijingwanwq.com/category/program-development/
https://www.shuijingwanwq.com/category/program-development/page/2/
https://www.shuijingwanwq.com/tag/cache/
https://www.shuijingwanwq.com/tag/cache/page/2/
https://www.shuijingwanwq.com/series-category/blog-management-practices/
https://www.shuijingwanwq.com/series/wordpress-cdn-global-acceleration-practice/
https://www.shuijingwanwq.com/series/wordpress-cdn-global-acceleration-practice/page/3/
https://www.shuijingwanwq.com/2026/
https://www.shuijingwanwq.com/2026/page/32/
https://www.shuijingwanwq.com/2026/07/
https://www.shuijingwanwq.com/2026/07/page/3/
https://www.shuijingwanwq.com/2026/07/30/
The rules also need to accommodate future pagination:
/series-category/blog-management-practices/page/2/
/2026/07/30/page/2/
Author archives were not included in the scope.
This is a single-author blog, and author archives have been disabled in Yoast SEO, so /author/.../ is not a currently valid public list page.
Only process canonical URLs with a trailing slash
The rules only match URLs ending with /.
URLs without a trailing slash, such as:
/tag/cache
/category/program-development
are first redirected via 301 by WordPress or existing canonicalization logic to:
/tag/cache/
/category/program-development/
They are then processed by the CDN rules.
This remains consistent with the previous handling for article detail pages.
4. Search Pages Must Retain Query Strings
You cannot simply delete query parameters for the homepage and all /page/数字/ URLs.
WordPress search pages rely on the s parameter:
https://www.shuijingwanwq.com/?s=autopoly
https://www.shuijingwanwq.com/page/2/?s=autopoly
The path for search pagination is itself also /page/2/.
If judged solely by the URL path, search pagination could be mistaken for standard homepage pagination, causing s=autopoly to be deleted and ultimately returning a standard article list.
Therefore, the list page branch must additionally require:
Query parameter s does not exist
I also excluded WordPress preview parameters from the list pages:
preview
preview_id
preview_nonce
5. EdgeOne Chinese Site Configuration
Instead of creating two separate rules for article detail pages and public list pages, I merged them into a single rule.
The rule name is:
www Article and Public List Page Query String Normalization
The outermost condition is only:
HOST equals www.shuijingwanwq.com
A limitation encountered during configuration
Initially, I also added at the outermost level:
Request method equals GET, HEAD
However, after adding the request method condition, the EdgeOne interface no longer allowed adding the “Custom Cache Key” operation.
Therefore, I ultimately removed the request method condition, kept only the HOST condition, and then used two internal branches for article detail pages and public list pages.
6. EdgeOne Article Detail Page Branch
The article detail page continues to use the previously verified regex:
^/[0-9]{4}/[0-9]{2}/[0-9]{2}/[0-9]+/$
For example:
/2026/07/27/20445/
This branch performs two operations:
- Custom Cache Key → Query String → Ignore All
- Origin Request Parameter Setting → Query String → Ignore All

Both operations need to be configured here.
Ignoring only the Cache Key prevents different parameters from generating different cache objects; however, on a cache miss, the query parameters could still be passed to the origin.
Conversely, removing only the origin parameters cannot guarantee that different parameter variants will reuse the same cache object.
Only by combining both can you simultaneously achieve:
Parameter variants share cache
+
Origin receives only clean URLs
7. EdgeOne Public List Page Branch
Public list pages use a ELSE IF branch at the same level as the article detail page.
The final regex used is:
^(?:/|/page/[1-9][0-9]*/|/category/(?:[^/]+/)+(?:page/[1-9][0-9]*/)?|/(?:tag|series|series-category)/[^/]+/(?:page/[1-9][0-9]*/)?|/[0-9]{4}(?:/[0-9]{2}){0,2}/(?:page/[1-9][0-9]*/)?)$
This regex covers:
- Homepage and homepage pagination
- Single-level or multi-level categories and their pagination
- Tags and their pagination
- Series and their pagination
- Series categories and their pagination
- Yearly, monthly, and daily archives and their pagination
It does not match article detail pages:
/2026/07/30/21863/
The list page branch also sets the following conditions:
Query parameter s: does not exist
Query parameter preview: does not exist
Query parameter preview_id: does not exist
Query parameter preview_nonce: does not exist
Additionally, a special endpoint exclusion condition is retained:
/(?:feed(?:/(?:rss2|rss|atom|rdf))?|embed|trackback)/?$
The operator is “regex does not match”.
This prevents feed, embed, or trackback within multi-level category paths from being treated as standard category hierarchies.
For example, the following URLs will not enter the public list page parameter-removal branch:
/category/program-development/feed/
/category/program-development/embed/
/category/program-development/trackback/
The list page branch also performs:
- Custom Cache Key → Query String → Ignore All
- Origin Request Parameter Setting → Query String → Ignore All

After saving and publishing, the entire rule consists of the article detail page branch and the public list page branch.

8. EdgeOne Test Results
After publishing the rule, I sent two requests each to 14 Chinese site list pages:
- The first request to the clean URL;
- The second request to the same URL, but with a unique test parameter appended.
For example:
https://www.shuijingwanwq.com/page/4/
https://www.shuijingwanwq.com/page/4/?swq_qs_test=random_value
The results for the homepage and homepage pagination both showed:
Clean URL: MISS
URL with parameter: HIT
Body hash identical
The TTFB for the first cold origin fetch of the homepage reached about 14.21 seconds, and /page/4/ reached about 14.68 seconds.
After the parameterized request reused the cache, the TTFB dropped to about 1.2 seconds.
This indicates that once a cache is established for a given list page, subsequent requests—even with random query parameters—do not need to re-execute a WordPress cold origin fetch taking nearly 15 seconds.
Category pages, category pagination, tag pages, and tag pagination also successfully showed:
MISS → HIT
And the body content was identical.
The following pages were also covered by the rule:
series-category- Series homepage
- Series pagination
- Yearly archive
- Monthly archive
- Daily archive
- Various archive pagination
Monthly archive showed two MISSes
During testing, the following two pages showed MISS → MISS:
/2026/07/
/2026/07/page/3/
However, the origin logs showed that both origin fetches received clean URLs without query parameters:
GET /2026/07/ HTTP/1.1
GET /2026/07/ HTTP/1.1
GET /2026/07/page/3/ HTTP/1.1
GET /2026/07/page/3/ HTTP/1.1
The origin did not receive:
?swq_qs_test=...
The two requests came from different EdgeOne IPs, suggesting they were likely routed to different edge nodes rather than the Query String rule failing.
9. EdgeOne Search Page Protection Verification
The search homepage still returns:
You are searching for autopoly - Yongye
Search pagination still returns:
You are searching for autopoly - Page 2 of 2 - Yongye
The content of the search page also differs from the standard homepage and standard pagination.
In the origin logs, the test parameter appeared only in the two search requests:
/?s=autopoly&swq_qs_test=...
/page/2/?s=autopoly&swq_qs_test=...
The other 14 sets of article or list page requests did not pass the test parameter to the origin.
This proves that:
- Standard list pages ignore the Query String in the cache key;
- On a cache miss, standard list pages do not pass the Query String to the origin;
- Search requests are correctly excluded because the
sparameter is present; - The search homepage and search pagination are unaffected.
10. Cloudflare English Site Configuration
The English site en.shuijingwanwq.com uses Cloudflare.
Here, instead of creating multiple new URL Rewrite Rules, I extended the existing article detail page Query String rule.
The rule name was changed to:
English Article and Public List Page Query String Normalization
The final expression is as follows:
(
http.host eq "en.shuijingwanwq.com"
and http.request.uri.query ne ""
and (
(
starts_with(http.request.uri.path, "/20")
and substring(http.request.uri.path, 5, 6) eq "/"
and substring(http.request.uri.path, 8, 9) eq "/"
and substring(http.request.uri.path, 11, 12) eq "/"
and ends_with(http.request.uri.path, "/")
and substring(http.request.uri.path, 12, -1) ne ""
and not (substring(http.request.uri.path, 12, -1) contains "/")
)
or
(
not any(http.request.uri.args.names[*] == "s")
and not any(http.request.uri.args.names[*] == "preview")
and not any(http.request.uri.args.names[*] == "preview_id")
and not any(http.request.uri.args.names[*] == "preview_nonce")
and (
http.request.uri.path eq "/"
or (
starts_with(http.request.uri.path, "/page/")
and ends_with(http.request.uri.path, "/")
and substring(http.request.uri.path, 6, -1) ne ""
and not (substring(http.request.uri.path, 6, -1) contains "/")
)
or (
(
starts_with(http.request.uri.path, "/category/")
or starts_with(http.request.uri.path, "/tag/")
or starts_with(http.request.uri.path, "/series/")
or starts_with(http.request.uri.path, "/series-category/")
)
and ends_with(http.request.uri.path, "/")
and not (
ends_with(http.request.uri.path, "/feed/")
or ends_with(http.request.uri.path, "/feed/rss2/")
or ends_with(http.request.uri.path, "/feed/rss/")
or ends_with(http.request.uri.path, "/feed/atom/")
or ends_with(http.request.uri.path, "/feed/rdf/")
or ends_with(http.request.uri.path, "/embed/")
or ends_with(http.request.uri.path, "/trackback/")
)
)
or (
starts_with(http.request.uri.path, "/20")
and substring(http.request.uri.path, 5, 6) eq "/"
and (
len(http.request.uri.path) eq 6
or (
substring(http.request.uri.path, 5, 11) eq "/page/"
and ends_with(http.request.uri.path, "/")
and substring(http.request.uri.path, 11, -1) ne ""
and not (substring(http.request.uri.path, 11, -1) contains "/")
)
or (
substring(http.request.uri.path, 8, 9) eq "/"
and (
len(http.request.uri.path) eq 9
or (
substring(http.request.uri.path, 8, 14) eq "/page/"
and ends_with(http.request.uri.path, "/")
and substring(http.request.uri.path, 14, -1) ne ""
and not (substring(http.request.uri.path, 14, -1) contains "/")
)
or (
substring(http.request.uri.path, 11, 12) eq "/"
and (
len(http.request.uri.path) eq 12
or (
substring(http.request.uri.path, 11, 17) eq "/page/"
and ends_with(http.request.uri.path, "/")
and substring(http.request.uri.path, 17, -1) ne ""
and not (substring(http.request.uri.path, 17, -1) contains "/")
)
)
)
)
)
)
)
)
)
)
)
This expression covers:
- Article detail pages
- Homepage and homepage pagination
- Category pages and their pagination
- Tag pages and their pagination
- Series pages and their pagination
- Series categories and their pagination
- Yearly, monthly, and daily archives and their pagination
Future pagination URLs like the following will also match:
/series-category/example/page/2/
/2026/07/30/page/2/
Cloudflare Rewrite Action
The action remains:
Path: Preserve
Query:
Rewrite to
Static
Value left empty
This preserves the original URL path and only rewrites the Query String to empty.
The rule is placed in the first position of the URL Rewrite Rules.
I initially had some doubts about placing it “first”, but subsequent testing proved that this order caused no conflicts and was not re-appended with query parameters by later rules.

11. Cloudflare Test Results
The English site testing covered:
- Article detail pages
- Homepage
- Homepage pagination
- Category pages
- Category pagination
- Tag pages
- Series pagination
- Yearly archive
- Monthly archive
- Daily archive
The homepage, homepage pagination, category pages, and tag pages all showed:
HIT → HIT
And the Age for both requests increased continuously.
For example:
Homepage Age: 5319 → 5329
Homepage pagination Age: 38470 → 38474
Category page Age: 38723 → 38728
Tag page Age: 38210 → 38214
This indicates that URLs with random parameters did not create new independent cache objects but reused the existing cache.
Category pagination showed a more direct result:
Clean URL: MISS
URL with parameter: HIT
URL with parameter request Age: 4
Daily archives also showed:
MISS → HIT
Different edge nodes causing two MISSes
Article detail pages, series pagination, and yearly archives previously showed:
MISS → MISS
However, the CF-Ray for the two requests fell on LAS and LAX respectively, indicating the requests hit different Cloudflare edge nodes.
The origin logs ultimately confirmed that even when two origin fetches occurred for these pages, the URLs Cloudflare passed to Nginx were still clean:
/2026/07/13/19426/
/category/blog-operations-en/page/2/
/series/wp-blog-multilingual-guide/page/3/
/2026/
/2026/07/
/2026/07/13/
No standard article or list requests carried the test parameter.
12. Cloudflare Search Page Protection Verification
The English search homepage still returns:
You are searching for wordpress - Yongye
Search pagination still returns:
You are searching for wordpress - Page 2 of 23 - Yongye
The content of the search page also differs from the standard homepage and standard pagination.
Throughout the origin logs, the test parameter appeared only in the two English search requests:
/?s=wordpress&swq_cf_qs_test=...
/page/2/?s=wordpress&swq_cf_qs_test=...
This proves the English site also achieved:
- Query String removal for standard article and list pages;
- Search requests retaining the
sparameter; - Search homepage and search pagination remaining unaffected.
13. Why HTML Hashes Sometimes Differ in Cloudflare Tests
In the EdgeOne Chinese site tests, the full body hash of the clean URL and the parameterized URL were basically identical.
On the Cloudflare English site, however, many pages showed:
same_body: no
Even if both requests were HIT from the same node, the full HTML hash could still differ.
This does not directly indicate a failure of cache normalization.
WordPress pages may contain fields that change with every response, or dynamic content injected by edge nodes or other components. Therefore, the SHA-256 of the full HTML file can only serve as an auxiliary reference.
The success of this normalization was judged primarily based on three pieces of evidence:
- Whether
CF-Cache-Statuschanged fromMISStoHIT; - Whether
Ageon the same node increased continuously; - Whether the Nginx origin logs still showed the test Query String.
The final origin logs proved that query parameters for standard article and list pages were indeed removed before the origin fetch.
14. Final Results
After completing the adjustments, both the Chinese and English sites implemented the same logic.
Chinese site EdgeOne:
Article detail pages and public list pages
→ Query parameters do not participate in Cache Key
→ Query parameters are not passed to the origin on cache MISS
→ Search parameter s retained
English site Cloudflare:
Article detail pages and public list pages
→ Query String rewritten to empty
→ Different parameter variants reuse canonical URL cache
→ On cache MISS, origin receives only clean paths
→ Search parameter s retained
Going forward, when the following requests appear:
?amp=1
?nonamp=1
?query-5-page=...
?query-62-page=...
?random=value
As long as they fall within the scope of article detail pages or supported public list pages, they will no longer continuously create independent cache objects and repeated origin fetches due to different parameters.
15. Limitations of This Optimization
Query String normalization can significantly reduce repeated origin fetches, but it cannot solve the underlying issue of a single cold origin fetch being too slow.
In this test, the cold origin fetch TTFB for the Chinese homepage and /page/4/ remained close to 15 seconds, indicating that the dynamic generation speed of WordPress list pages still needs continued observation.
However, this adjustment at least resolved a very important issue:
The same time-consuming page will no longer be repeatedly executed as dozens of different pages due to dozens of meaningless query parameters.
For a server with only a 1-core CPU and pm.max_children = 7, reducing unnecessary PHP requests is more practical than simply increasing the number of PHP-FPM processes.
Next, I will continue to monitor the following metrics:
- Number of EdgeOne 524 errors
- Number of Nginx 499 errors
- PHP-FPM
max_childrenalerts - Cold origin fetch latency for homepage and list pages
- EdgeOne and Cloudflare cache hit rates
Based on subsequent data, I will then determine whether further optimization of WordPress list page queries, W3 Total Cache, Redis object cache, or origin page generation speed is necessary.

