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

WordPress CPU Spikes Again: How Dynamic Parameters Bypass CDN and W3TC Page Caching

Figure 7: With a Query String, W3TC shows Requested URI contains query

作者:

During the previous round of WordPress 502 errors, I ultimately discovered that a batch of abnormally high-frequency requests carrying the Sogou web spider/4.0 User-Agent continuously bombarded the origin server, causing the 1-core CPU of the Alibaba Cloud ECS to stay fully loaded for extended periods and bringing the PHP-FPM queue to the verge of exhaustion.

After adding a *Sogou* User-Agent interception rule in EdgeOne, the 502 errors quickly disappeared, and the CPU, Load, and PHP-FPM queue returned to normal.

I initially thought this incident was mostly resolved.

However, after installing and restoring Alibaba Cloud host monitoring, I quickly noticed:

The CPU would still intermittently spike to 95%–100%.

This meant that the Sogou UA was merely a trigger for the previous severe outage, and a more fundamental issue remained behind the scenes.

After continuing the investigation this time, I ultimately narrowed the problem down to:

Front-end requests with arbitrary Query Strings can simultaneously reduce CDN cache hits and invalidate the W3 Total Cache Disk: Enhanced page cache, ultimately sending them straight into PHP-FPM.

This is a more worthwhile issue to address than simply blocking a specific crawler.

Moreover, it has also shifted the direction of my subsequent WordPress caching architecture optimization.


1. Sogou is already blocked, so why does the CPU still spike to 100%?

After restoring Alibaba Cloud host monitoring, I configured CPU, memory, disk, and public network bandwidth alarms for the ECS.

Soon after, I received multiple CPU Critical alarms.

For example:

Plaintext
2026-07-26 22:43  CPU = 100%
2026-07-27 01:34  CPU = 100%
2026-07-27 07:40  CPU = 99.26%

These alarms were not caused by the CPU remaining consistently high all day.

Looking at the daily monitoring curve:

Plaintext
Normal CPU: approximately 20%–45%
Occasionally spikes suddenly
Reaches 95%–100% briefly
Then recovers

Memory, on the other hand, remained consistently stable.

Figure 1: Daily CPU, memory, and system Load curves in Alibaba Cloud CloudMonitor
Figure 1: Daily CPU, memory, and system Load curves in Alibaba Cloud CloudMonitor

This first indicates:

This 1-core 2GB ECS is not under-resourced all day; rather, it experiences burst loads during specific time windows.

Therefore, I did not immediately upgrade to 2 cores and 4GB, but instead continued looking for the source of the CPU spikes.


2. Initially, I suspected WordPress Cron

The server currently disables WP-Cron triggered by visitor requests:

Plaintext
DISABLE_WP_CRON = 1

It then actively executes once every 5 minutes via system Cron:

Plaintext
*/5 * * * * cd /data/wwwroot/www.shuijingwanwq.com && /usr/local/php/bin/php wp-cron.php >/dev/null 2>&1

This frequency easily leads to the suspicion:

Plaintext
Executing wp-cron.php every 5 minutes

Certain WordPress scheduled tasks are heavy

CPU gets maxed out

In the WordPress Cron list, there is indeed a task that runs once every 5 minutes:

Plaintext
clarity_collect_batch

Because of this, Cron temporarily became the prime suspect.

Figure 2: Server root crontab and WordPress Cron task list
Figure 2: Server root crontab and WordPress Cron task list

3. Observing a round of WP-Cron in real time revealed it is actually very lightweight

To confirm, instead of modifying Cron, I directly observed the next round of wp-cron.php.

Around 10:00:

Plaintext
10:00:02  wp-cron not running

10:00:08
php wp-cron.php
CPU ≈ 31.2%

10:00:13
wp-cron already finished

That is to say:

A normal WP-Cron run takes only a few seconds.

It clearly cannot explain:

Plaintext
CPU ≥95%
for several consecutive minutes

Therefore, WP-Cron cannot serve as the primary explanation for the current CPU Critical alarms.

Of course, certain special Cron Hooks might still occasionally be heavy, but the troubleshooting priority should now be lowered.


4. After narrowing monitoring to 15 seconds, I confirmed it was indeed continuously fully loaded at 07:40

Next, I narrowed the Alibaba Cloud process monitoring time range to:

Plaintext
07:35~07:45

The statistical cycle was also shortened to about 15 seconds.

This time, the CPU curve was very clear:

Plaintext
Around 07:37
CPU starts approaching 100%

Lasts for several minutes

After about 07:40
Gradually recovers

So:

Plaintext
07:40 CPU 99.26%

was not a monitoring false alarm.

The server was indeed in a continuously fully loaded state at that time.

Figure 3: 15-second CPU monitoring curve from 07:35–07:45
Figure 3: 15-second CPU monitoring curve from 07:35–07:45

However, Alibaba Cloud’s process TopN did not fully capture the high-usage processes during these few minutes, so I went back to the server logs.


5. The real anomaly appeared in PHP-FPM

In the PHP-FPM main log, a very critical line quickly appeared:

Plaintext
[27-Jul-2026 07:36:16]
server reached max_children setting (7)

That is to say:

Plaintext
pm.max_children = 7

were all exhausted.

At the same time, starting from 07:36, there were a large number of:

Plaintext
index.php
executing too slow
3–4 seconds

Moreover, these requests were not background tasks, nor were they Cron.

They were basically all front-end requests:

Plaintext
/index.php
/index.php?amp=1
/index.php?noamp=mobile
/index.php?nonamp=1
/index.php?query-62-page=...

From 07:36 to 07:42, there were consistently PHP-FPM slow requests.

Figure 4: PHP-FPM reaching max_children=7 and continuously experiencing front-end slow requests
Figure 4: PHP-FPM reaching max_children=7 and continuously experiencing front-end slow requests

At this point, the failure chain had changed:

Plaintext
Large number of front-end requests

Enter PHP-FPM

All 7 workers busy

Single-core CPU approaches 100%

New requests continue to queue

So Cron was not the main contradiction behind this CPU spike.


6. The Slow Log further proves: WordPress is fully rendering these requests

In the PHP-FPM Slow Log, many real WordPress call chains can be seen.

Including:

Plaintext
Gutenberg
Polylang
W3 Total Cache
Redis Object Cache
Post Views Counter
WP_Query
mysqli_query

For example, some of these requests go through:

Plaintext
W3 Total Cache

Redis Object Cache

Polylang

Gutenberg block rendering

This shows that Redis Object Cache is indeed still working.

In addition, there are multiple calls to:

Plaintext
Post Views Counter

pvc_get_most_viewed_posts()

WP_Query

mysqli_query()

An important distinction needs to be made here:

Object Cache being effective does not mean Page Cache is effective.

Redis can reduce some database query and object retrieval costs, but as long as WordPress PHP has already started:

Plaintext
Plugins
Themes
Gutenberg
Polylang
Dynamic blocks
Database queries

still need to continue executing.

And what can truly give PHP-FPM a complete break is:

A full-page Page Cache hit.


7. Only 687 requests in 8 minutes, yet enough to max out the server

I then analyzed:

Plaintext
07:35~07:42

the Nginx logs for these 8 minutes.

Total number of requests:

Plaintext
687

Status codes:

Plaintext
499   367
200   173
301    97
404    44
307     5
403     1

Among them, 499 accounted for over half.

This usually means:

The client actively disconnected before the server finished responding.

Combined with the fact that PHP-FPM was already at 7/7 and request execution times exceeded 3 seconds, the 499 errors seem more like a consequence of server overload.


8. The traffic is not from a single IP, but clearly distributed

Among the top source IPs, no single IP made hundreds of requests.

The highest was only:

Plaintext
17
16
14
13
...

But the sources were distributed across a large number of IPs and network segments.

Moreover, some of these IPs overlapped with the sources of the anomalous traffic carrying the Sogou UA from the previous day, for example:

Plaintext
1.71.146.14
122.246.2.213
1.71.147.97
222.79.116.117
1.71.147.232

Therefore, there is a possibility:

After blocking the *Sogou* UA the previous day, some automated requests continued to access the site using other User-Agents.

However, since User-Agents can be forged, I still do not intend to simply classify all these requests as coming from a specific bot.

What is truly more noteworthy is their access behavior.


9. User-Agent can no longer serve as a reliable sole condition for judgment

This batch of requests mixed:

Plaintext
Chrome
Firefox
Safari
Android Chrome

as well as explicit bots:

Plaintext
MJ12bot
Googlebot
meta-externalagent
Amazonbot

Many ordinary browser UAs even looked completely human.

So:

Plaintext
UA = Sogou
→ Block

can only solve a very small category of cases.

If automated traffic directly disguises itself as:

Plaintext
Chrome
Safari
Firefox

continuing to patch by UA becomes meaningless.


10. What is truly noteworthy is the Query String

Among these 687 requests:

Plaintext
amp=1            106
noamp=mobile      33
nonamp=1          49
query-62-page     59

There were also:

Plaintext
/page/119?query-62-page=111
/page/134/?query-62-page=135

...?amp=1
...?nonamp=1
...?noamp=mobile

This led me to start suspecting:

The real problem might not be a specific crawler, but rather that URLs with parameters can bypass the cache themselves.

Figure 5: Source IP, User-Agent, URI, and status code statistics from 07:35–07:42
Figure 5: Source IP, User-Agent, URI, and status code statistics from 07:35–07:42

11. So I tested the W3TC Page Cache directly

To confirm, I bypassed the CDN and requested the origin server directly via 127.0.0.1.

First, I tested the normal homepage:

Plaintext
/

First time:

Plaintext
HTTP 200
TTFB = 9.03 seconds

Second time:

Plaintext
HTTP 200
TTFB = 0.11 seconds

W3TC output:

Plaintext
Using page cache Disk: Enhanced

And the second time:

Plaintext
Served from

The time was completely consistent with the first time.

This proves that the Page Cache for normal URLs is working perfectly:

Plaintext
First time
WordPress fully generates

↓ Writes to Page Cache

Second time
Directly reuses HTML
Figure 6: Normal URL takes about 9 seconds the first time, and only about 0.1 seconds the second time
Figure 6: Normal URL takes about 9 seconds the first time, and only about 0.1 seconds the second time

12. After adding a parameter, the W3TC page cache is directly invalidated

Next, I tested:

Plaintext
/?amp=1

First time:

Plaintext
TTFB = 11.11 seconds

Second time:

Plaintext
TTFB = 9.42 seconds

W3TC explicitly output:

Plaintext
Disk: Enhanced (Requested URI contains query)

And the two Served from times were different.

That is to say:

The second request did not reuse the page generated by the first request.

?noamp=mobile:

Plaintext
First time: 7.94 seconds
Second time: 9.01 seconds

?nonamp=1:

Plaintext
First time: 7.70 seconds
Second time: 7.83 seconds

The situation was exactly the same.

Figure 7: With a Query String, W3TC shows Requested URI contains query
Figure 7: With a Query String, W3TC shows Requested URI contains query

At this point, the core mechanism behind this CPU spike is very clear.


13. The real danger is not amp, but “any parameter”

It is easy to initially think of:

Plaintext
amp
noamp
nonamp

and just block or redirect these three parameters.

But I quickly realized:

This treats the symptoms, not the root cause.

Because the attacker does not need to use these parameters at all.

They can easily generate:

Plaintext
?abc=1
?abc=2
?foo=123
?random=999
?test=xxxxx

The parameter names and values can theoretically vary infinitely.

As long as the current caching mechanism determines:

Plaintext
Query String exists

W3TC Page Cache not reused

Then the attacker essentially possesses a very simple:

switch to force WordPress to render dynamically.

For a 1-core ECS, this is particularly dangerous.


14. This also explains why 687 requests can cause a noticeable impact

A normal cached request might only take:

Plaintext
0.1 seconds

Whereas a dynamic request with a Query String takes:

Plaintext
7–11 seconds

The difference is nearly two orders of magnitude.

If 7 PHP-FPM Workers simultaneously process such requests:

Plaintext
7 workers
×
each lasting several seconds

The server will quickly enter:

Plaintext
pm.max_children = 7

Requests queuing

CPU 100%

Responses slowing down

499 errors increasing

This is no longer a question of “whether the traffic volume is large.”

Rather:

The same volume of traffic incurs completely different costs depending on whether it enters the server as cached requests or dynamic requests.


15. Therefore, I temporarily do not plan to upgrade to 2 cores and 4GB

After this analysis, I am even less inclined to immediately upgrade the ECS.

Because normally:

Plaintext
CPU around 20%–45%
Memory around 40%–50%

The real 100% peaks mainly occur when the cache is heavily bypassed.

Upgrading to 2 cores and 4GB can increase capacity, but it cannot solve:

Plaintext
Arbitrary Query Strings

Continuously generating dynamic WordPress requests

Today it was 687 requests.

In the future, it could easily become:

Plaintext
1000
2000
5000

So a more reasonable sequence should be:

Plaintext
First fix cache bypassing

Continue observing CPU

Normal business still frequently maxes out

Then consider upgrading the server

16. I am also not going to deeply modify W3TC

Theoretically, I could study W3TC to make:

Plaintext
/article/
/article/?foo=1
/article/?foo=999

share the same Page Cache.

For some known parameters, W3TC itself also has related mechanisms.

But the real problem is:

There can be an infinite number of parameters.

What I really want is:

Plaintext
Default to ignoring meaningless Query Strings

Only a few parameters that actually change content
act as exceptions

If I were to modify:

Plaintext
advanced-cache.php
W3TC internal caching logic
Cache Key

to achieve this, not only would troubleshooting time continue to increase, but future W3TC and WordPress upgrades could also add maintenance costs.

This has already deviated from my goal this time:

Resolve the issue of the CPU being maxed out by anomalous requests as quickly as possible.


17. A solution more suited to me: let the CDN handle URL normalization

Therefore, I currently plan to handle this at the CDN layer.

Chinese site:

Plaintext
www.shuijingwanwq.com
→ Tencent EdgeOne

English site:

Plaintext
en.shuijingwanwq.com
→ Cloudflare

The target logic is essentially the same.

For public pages confirmed to “not depend on Query String content”:

Plaintext
/article/
/article/?foo=1
/article/?foo=999

The CDN layer should treat them as much as possible as:

Plaintext
The same cache object

If the CDN already has a HIT:

Plaintext
Return directly

If the CDN MISSes:

Plaintext
Clean meaningless parameters during origin pull

Origin server receives clean URL

W3TC Disk: Enhanced

Continues to reuse normal Page Cache

Thus ultimately forming:

Plaintext
Client
/article/?random=123

↓ CDN

Ignores meaningless Query String

↓ HIT

Returns directly

↓ MISS

Origin pull:
/article/

↓ W3TC

Hits Disk: Enhanced Page Cache

↓ PHP-FPM

No need to fully execute WordPress

This better fits my current needs than modifying W3TC’s internal logic.


18. Why not just ignore Query Strings site-wide this time

There is also a very important boundary here.

Not all WordPress parameters are meaningless.

For example:

Plaintext
?p=123
?s=wordpress
?page_id=123
?preview=true

As well as what appeared in this log:

Plaintext
query-62-page

Some of these parameters do actually change the page content.

Therefore, you cannot simply:

Plaintext
All Query Strings site-wide
→ Ignore all

Otherwise, the following might occur:

Plaintext
/?s=PHP
/?s=WordPress

receiving the same cached page.

Even:

Plaintext
?p=100
?p=200

could show incorrect content.

So the core of the subsequent configuration is not:

“Ignore all parameters.”

Rather:

Only create rules for URL types suitable for strong caching, where parameters usually should not change content.

My primary consideration right now is:

Plaintext
WordPress article detail pages

These are the most numerous, highest-traffic, and most semantically explicit type of public pages.


19. Both EdgeOne and Cloudflare can adopt a similar approach

The Chinese site’s EdgeOne can handle this from two aspects:

Plaintext
Cache Key
+
Origin pull request parameters

The English site’s Cloudflare also has corresponding capabilities:

Plaintext
Cache Rule
+
Transform Rule

Therefore, even though the two sites use different CDNs, they can ultimately maintain the same architectural logic:

Plaintext
www / EdgeOne
        \
         → Query String normalization
        /
en / Cloudflare


Origin server receives clean public page URL


W3TC Disk: Enhanced

This way, subsequent maintenance won’t turn into two completely different approaches.


20. The biggest takeaway from this troubleshooting is not actually finding a specific crawler

The initial problem appeared to be:

Plaintext
Sogou Spider

CPU 100%

502

So it was natural to think:

Just block Sogou and it’ll be fine.

But after restoring monitoring, new CPU Critical alarms prompted me to dig deeper, ultimately revealing that what truly deserves long-term attention is:

Plaintext
Automated requests

Large number of URLs with Query Strings

CDN failing to fully absorb them

W3TC Disk: Enhanced not reusing Page Cache due to Query String

Requests entering PHP-FPM

Fully executing WordPress

Single request taking several seconds

A small amount of concurrency is enough to max out a single-core server

Compared to “blocking a User-Agent,” this is clearly a more fundamental issue.

And it also gave me a renewed understanding of one point:

WordPress performance optimization cannot just look at whether caching exists, but also at whether requests can consistently fall onto the same cache key.

Caching exists, but if attackers or crawlers can continuously bypass it by arbitrarily adding parameters, then the protective power of the cache is greatly diminished.


21. Current stage: stop digging further into the server for now

At this point, I believe this round of issues has been located clearly enough.

I temporarily do not plan to:

Plaintext
Upgrade ECS
Increase pm.max_children
Modify W3TC internal code
Block parameters one by one
Block IPs one by one
Continue tracking a specific User-Agent

What really needs to be done in the next stage is:

Plaintext
EdgeOne

Verify Query String cache normalization for article detail pages

Cloudflare

Configure equivalent rules


Test URLs with random parameters


Confirm CDN HIT


When forcing a MISS, confirm W3TC can still hit the clean URL Page Cache


Continue observing whether CPU Critical alarms significantly decrease

I plan to document these specific operations separately in the next article.

Because compared to this article’s “why the problem occurred,” the next article is better suited to fully document:

How to establish Query String cache normalization rules for WordPress article detail pages in EdgeOne and Cloudflare.


In closing

This troubleshooting actually went through several different stages:

Plaintext
502

CPU 100%

PHP-FPM queue exhausted

Discovered anomalous Sogou UA traffic

EdgeOne interception

502 recovered

Restored Alibaba Cloud host monitoring

CPU Critical continued to appear

Suspected WordPress Cron

Cron tested normal

Located PHP-FPM front-end slow requests

Discovered a large number of URLs with Query Strings

Tested W3TC directly

Confirmed Query String causes Page Cache to not be reused

Ultimately decided to handle cache normalization at the CDN layer

So this time, the key to truly solving the problem was not a specific command, nor a specific plugin.

Rather, it was progressively tracing:

“What is consuming the CPU”

down to:

“Why do these requests fall onto PHP.”

Now the answer is quite clear.

The next step is to translate this analysis into actual production configurations for EdgeOne and Cloudflare.

系列导航

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

我是拥有 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 来减少垃圾评论。了解你的评论数据如何被处理