The day before, I had just finished optimizing the CDN cache for WordPress article detail pages, focusing on the issue where URLs with query strings could not effectively reuse the cache.
I originally thought that after handling this type of cache penetration, the server CPU alerts would ease.
But on the afternoon of July 28, 2026, an Alibaba Cloud ECS alert showed the CPU usage approaching 100% again.
This time, instead of continuing to focus on article detail pages and query strings, I started from Alibaba Cloud monitoring and checked all the way through:
- Nginx Access Log
- PHP-FPM logs and Slowlog
- W3 Total Cache Object Cache
- Redis Slowlog
- W3TC Disk Enhanced Page Cache
- Actual response times for Cold Cache and Cache HIT
Ultimately, I found:
This CPU saturation was not a simple continuation of the “detail page + query string” issue from the previous article. The core problem was that the server only had 1 vCPU, and when a large number of different URLs missed the Page Cache, they had to enter WordPress to dynamically generate the page. Just a few simultaneous high-cost Cold MISSes were enough to keep all PHP-FPM workers busy and max out the single CPU core.
One highly representative page was:
/page/9/
On the first Cold MISS, the TTFB reached:
10.602379 seconds
After the W3TC Page Cache was built, the second access only took:
0.016091 seconds
This gap ultimately became the key basis for the optimization direction in this round.
1. Alibaba Cloud CPU Sustained Near 100% Again
This alert occurred on the afternoon of July 28, 2026.
Checking the Alibaba Cloud ECS OS monitoring, I could see the CPU rapidly rising to near 100% starting around 14:33, and maintaining that level until about 14:42, when it suddenly dropped.
At the same time, the overall memory usage remained stable, without experiencing exhaustion in sync with the CPU.
Therefore, the initial conclusion was:
This was not a typical case of insufficient memory, but rather a sustained saturation of CPU compute resources.

After further opening Alibaba Cloud process monitoring, I could see that the high-CPU processes were basically:
php-fpm
php-fpm: pool www
This indicated that the main source of the load this time was not Nginx, but rather WordPress PHP dynamic requests.

2. Nginx Shows a Large Number of 499s
The Nginx Access Log for the production site is currently located at:
/data/wwwlogs/www.shuijingwanwq.com_nginx.log
After extracting the time window from 14:32 to 14:44, I obtained a total of:
844 requests
The status code distribution was as follows:
499 499
200 153
301 130
404 62
That is to say:
499s accounted for about 59%.
An Nginx 499 means the client actively closed the connection before the server finished responding.
Seeing 499s alone does not prove that PHP-FPM is the root cause.
But after aggregating by minute, the situation became very clear:
14:32 total=53 499=0
14:33 total=73 499=49
14:34 total=153 499=147
14:35 total=38 499=30
14:36 total=46 499=39
14:37 total=59 499=47
14:38 total=61 499=42
14:39 total=56 499=31
14:40 total=78 499=58
14:41 total=89 499=49
14:42 total=65 499=7
14:43 total=42 499=0
14:44 total=31 499=0
This was almost perfectly in sync with the Alibaba Cloud CPU curve:
14:33
CPU near 100%
499s start appearing in large numbers
14:34~14:41
CPU remains high for a long time
Large number of 499s
14:42
Pressure starts dropping rapidly
14:43
499s return to 0
Therefore, these 499s were more likely:
The result of clients, the CDN, or crawlers waiting too long and disconnecting early after PHP-FPM became severely congested.
3. This Was Not Caused by a Single IP or URL
Initially, I suspected that a single page was being hit with a large number of requests.
But after analyzing the URLs, I found the requests were highly dispersed.
The first-level path distribution was roughly as follows:
/tag/ 373
/category/ 102
/en/ 85
/2026/ 83
/page/ 76
/ 25
/wp-json/ 24
...
Among them:
/tag/
/category/
/page/
These three types of pages combined accounted for about 65% of the requests in this time window.
The specific URLs were also highly dispersed, for example:
/tag/app/
/tag/mysql-5-7/page/3
/tag/electron/
/page/9
/page/19?query-62-page=61
/page/109?query-62-page=127
/category/.../page/17/?noamp=mobile
...
The source IPs were also quite dispersed, with no single IP accounting for the majority of requests.
The User-Agents included many automated crawlers, such as:
Sogou web spider
bingbot
SemrushBot
Baiduspider
meta-externalagent
ClaudeBot
Googlebot
Therefore, this was more like:
Multiple crawlers or automated programs simultaneously traversing a large number of different URLs for historical articles, tags, categories, and pagination, rather than a simple flood request from one IP to a single address.
4. This Time It Was Not a Query String-Driven Issue
The day before, I had just dealt with the issue where:
Article detail page + Query String
prevented the CDN and W3TC cache from being effectively reused.
So naturally, I initially suspected the same problem had reoccurred.
But after analyzing the 499s this time, I found:
With Query String: 135
Without Query String: 364
That is, approximately:
73% of the 499 requests had no Query String at all.
Therefore, the previous day’s optimization was still meaningful, but it could not solve this CPU alert.
The two types of problems can be distinguished as follows:
The previous article focused on solving:
Same article detail page
+
Different meaningless Query Strings
+
Causing unnecessary cache fragmentation or penetration
What I faced this time was more like:
Large number of different URLs
+
Each experiencing its first Cold Cache
+
Simultaneously entering PHP for dynamic generation
5. All 7 PHP-FPM Workers Were Exhausted
PHP-FPM currently has Slowlog enabled:
request_slowlog_timeout = 3s
slowlog = /usr/local/php/var/log/slow.log
request_terminate_timeout = 600s
This means that if a single PHP request executes for more than 3 seconds, it will be logged in the Slowlog.
After checking the PHP-FPM logs for this incident window, a very critical entry appeared:
[28-Jul-2026 14:32:57]
WARNING: [pool www] server reached max_children setting (7), consider raising it
This meant:
Before the machine’s CPU officially entered a prolonged 100% plateau, all 7 PHP-FPM workers were already busy.
Moreover, starting from 14:32, multiple workers continuously showed:
executing too slow
Throughout the 14:32–14:44 time window, a total of:
287
instances of executing too slow were recorded.
The PHP-FPM log explicitly recorded that max_children=7 was exhausted, and there were already multiple dynamic requests exceeding 3 seconds prior to this.
Taking PID 49712 as an example, Alibaba Cloud process monitoring showed that it maintained a high CPU usage for a long time during the machine-wide CPU alert, and dropped rapidly after about 14:42.

At this point, the operational process of the CPU alert was basically clear:
Dynamic PHP requests slow down
↓
PHP-FPM workers gradually occupied
↓
All 7 workers busy
↓
New requests start waiting
↓
CPU remains near 100% for a long time
↓
Clients wait too long
↓
Nginx shows a large number of 499s
6. Slowlog Initially Pointed Suspicions at Gutenberg, W3TC, and Polylang
Upon further analysis of the PHP-FPM Slowlog, the files with the highest frequency in the complete call stacks included:
1237 wp-includes/class-wp-block.php
760 Gutenberg navigation.php
190 W3TC ObjectCache_WpObjectCache_Regular.php
189 W3TC ObjectCache_WpObjectCache.php
188 W3TC Cache_Redis.php
185 Polylang translatable-object.php
Aggregated by plugin:
1149 gutenberg
706 w3-total-cache
313 polylang
16 tms-extensions-polylang
6 post-views-counter
6 organize-series
...
This initially made it easy to think that:
Gutenberg Navigation
↓
taxonomy / term
↓
Polylang
↓
W3TC Object Cache
↓
Redis
was the main problem.
But the complete call stack only indicates that:
these components participated in the slow requests.
It does not show which step actually consumed the most time.
So I further analyzed:
At the exact moment the PHP request exceeded 3 seconds and a Slowlog snapshot was captured, what function was PHP executing?
7. About 73% of Slowlog Snapshots Stopped at Redis GET/MGET
The results were highly concentrated:
135 get() W3TC Cache_Redis.php
53 mget() W3TC Cache_Redis.php
Total:
188 / 257 ≈ 73%
In contrast:
18 Gutenberg Theme JSON sanitize()
7 merge()
3 mysqli_query()
2 Navigation get_inner_blocks()
...
As a result, the investigation focus temporarily shifted to:
W3TC Object Cache → Redis
After checking W3TC’s Cache_Redis.php, I found that the actual read logic contained:
$v = $accessor->get( $storage_key );
Therefore, the frequent appearance of get() in the Slowlog did indeed correspond to the underlying Redis Object Cache reads.
8. Redis Also Experienced Genuine Slow Requests
Redis’s overall current state did not show any obvious capacity issues.
Memory:
used_memory 517.82 MB
used_memory_peak 556.27 MB
maxmemory 1.00 GB
And:
evicted_keys 0
blocked_clients 0
rejected_connections 0
Object Cache cumulative:
keyspace_hits 149242618
keyspace_misses 1894018
The hit rate was approximately:
98.75%.
Therefore, there were no signs of Redis evicting large amounts of cache due to memory exhaustion.
However, the Redis Slowlog did contain requests taking tens of milliseconds.
For example:
GET optionsalloptions
approx. 87.6 ms
Additionally, there was one entry:
MGET terms...
followed directly by:
... (675 more arguments)
This meant that a single MGET read several hundred Term Cache Keys.
It temporarily seemed that:
Redis was very likely the root cause of the server slowdown.
Until I continued checking the ECS CPU core count.
9. What Truly Changed the Diagnosis: This Server Only Has 1 vCPU
Executing:
nproc
The result was only:
1
That is:
The entire ECS has only one CPU core.
At the same time, Redis was also running on the local machine:
redis-server 127.0.0.1:6379
This suddenly tied together many of the previously seemingly unrelated issues.
The server was actually forcing:
PHP-FPM Worker × 7
Redis
Nginx
Other system processes
to compete for the single CPU core.
Therefore, when 7 PHP-FPM workers simultaneously executed relatively heavy WordPress dynamic requests, it easily formed a cycle:
Heavy PHP computation
↓
Single CPU core saturates
↓
Redis also needs to compete for CPU
↓
Object Cache GET/MGET latency increases
↓
PHP requests take longer to complete
↓
Workers cannot be released for a longer time
↓
System remains at high CPU
So:
Redis did experience slow requests, but it was likely both a hotspot and a victim slowed down by CPU saturation, rather than the initial point of failure.
This also meant that I could not simply follow the PHP-FPM suggestion to:
consider raising it
and increase:
pm.max_children = 7
to 14 or higher.
A 1 vCPU machine does not gain more compute resources just because the number of PHP workers increases.
10. The Truly Key Question: Is W3TC Page Cache Actually Fast After a HIT?
Since the problem likely stemmed from a large number of requests entering PHP, the next most important thing was to check whether W3 Total Cache Disk Enhanced was actually working.
Current configuration:
Page Cache:Enabled
Engine:Disk Enhanced
Cache Query String:false
I bypassed the CDN and directly tested several types of URLs via the origin server.
Tag Page
/tag/app/
Three TTFBs:
0.023816 seconds
0.087154 seconds
0.016832 seconds
Category Page
/category/program-development/smslib/yuntongxun/
Results:
0.160287 seconds
0.079870 seconds
0.052548 seconds
Article Detail Page
/2026/03/27/9383/
Results:
0.045637 seconds
0.016188 seconds
0.015945 seconds
These data show that:
When W3TC Disk Enhanced already has an available cache, the origin server itself can be very fast.
The truly abnormal one was the following paginated page.
11. A Page Going from 10.6 Seconds to 0.016 Seconds
Testing:
/page/9/
First time:
HTTP=200
TTFB=10.602379s
Second time:
TTFB=0.016091s
Third time:
TTFB=0.031990s
Meanwhile, after the first access, a new file was generated:
wp-content/cache/page_enhanced/www.shuijingwanwq.com/page/9/_index_slash_ssl.html
The timestamp was exactly:
2026-07-28 16:14:01
Therefore, this almost perfectly reproduced:
First time
Page Cache MISS
↓
Enters PHP-FPM
↓
WordPress full dynamic generation
↓
10.6 seconds
↓
Writes to Disk Enhanced
Second time
Directly uses Page Cache
↓
0.016 seconds
So the most important finding of this investigation was not:
W3TC Page Cache is not working.
Quite the opposite:
W3TC is very fast after a HIT, but the cost of a Cold MISS is extremely high.
12. Why Can Just Over a Hundred Requests Per Minute Max Out the Server?
This time, the minute with the most requests at 14:34 only had about:
153 requests
Looking at the number alone, it does not seem particularly high.
But placed in the current environment:
1 vCPU
+
WordPress block theme
+
Many plugins
+
Some Cold MISS pages taking several seconds or even 10 seconds
the nature of the situation completely changed.
If crawlers continuously access:
/tag/A/
/tag/B/
/tag/C/
/category/A/
/category/B/
/page/9/
/page/19/
/page/109/
...
Every URL could be an independent Cold MISS.
Even if every page can be cached after generation:
URL A: First MISS
URL B: First MISS
URL C: First MISS
URL D: First MISS
Just a few simultaneous high-cost dynamic requests are enough to occupy all 7 PHP-FPM workers.
13. W3TC Page Cache Is Already Approaching 1 GB
At that time:
wp-content/cache/page_enhanced
had reached:
918 MB
Total number of files:
7320
Cache file age distribution:
Under 1 hour: 1414
1–2 hours: 1564
2–6 hours: 4342
Over 6 hours: 0
The W3TC configuration at that time was:
Maximum lifetime: 3600 seconds
Garbage Collection Interval: 3600 seconds
Cache Preload: Disabled
Seeing this, I initially suspected:
Does the Page Cache expire every hour and then constantly regenerate?
But actual testing showed that the situation was not that simple.
14. Caches Over 3 Hours Old Can Still Result in a Direct HIT
I found the following in the existing cache:
/2023/11/21/12193/
Cache generation time:
2026-07-28 12:48:52
And the test had reached approximately 16:21.
Requesting it at this point:
TTFB=0.038573s
Before and after the request:
mtime unchanged
size unchanged
inode unchanged
The bottom of the page also explicitly showed:
Served from: www.shuijingwanwq.com @ 2026-07-28 12:48:52 by W3 Total Cache
Therefore, it can be confirmed that:
Caches over 3 hours old on disk can still be returned directly; they are not immediately deleted from disk after 3600 seconds, nor are they necessarily regenerated on every request.
So, I also abandoned the overly simplistic understanding of:
“Everything becomes a Cold MISS again after 3600 seconds.”
This overly simplistic understanding.
15. Cache Preload Is Not Enabled
W3TC currently has:
pgcache.prime.enabled = false
Meaning Page Cache preloading is not enabled.
In theory, Cache Preload can generate pages in advance, thereby reducing the probability of real visitors incurring a Cold MISS.
But this time, I did not enable it for the time being.
The reason is simple:
The server only has 1 vCPU
And I had already actually encountered:
/page/9/
Cold MISS = 10.6 seconds
For a site with a large number of articles, tags, categories, and pagination, actively generating a large batch of page caches could itself create sustained CPU pressure.
So at this stage, I preferred:
To keep the caches that already consumed a lot of CPU to generate for as long as possible, rather than actively initiating a new massive wave of preloading.
16. Ultimately, Only One Parameter Was Changed: 1 Hour → 12 Hours
In the end, I did not modify Redis, did not adjust PHP-FPM max_children, and did not enable Cache Preload.
I only adjusted:
Performance → Page Cache → Advanced → Maximum lifetime of cache objects
Before modification:
3600 seconds
Which is: 1 hour.

I then changed it to:
43200 seconds
Which is:
12 hours.

Other configurations remained unchanged:
Maximum lifetime:
43200 seconds
Garbage Collection Interval:
3600 seconds
Cache Preload:
Disabled
Cache Query String:
Disabled
The purpose of doing this was not to claim that “12 hours is definitely the optimal value.”
Rather, it was to make a simple, low-risk, and easily verifiable adjustment first:
Reduce the probability of the same historical page repeatedly entering high-cost dynamic generation.
17. After Saving, I Did Not Click “Empty All Caches”
I consider this step very important.
After modifying the W3TC settings, right next to it in the dashboard was the:
Empty All Caches
button.
But this time, I did not click it.
The reason comes precisely from the biggest finding of this investigation:
Cold MISS: 10.6 seconds
Cache HIT: 0.016 seconds
There are already over 7,000 cache files on disk now.
If I were to actively Purge All just to “start fresh” with the configuration:
Large number of existing warm caches disappear
↓
Crawlers traverse again
↓
Large number of URL Cold MISSes
↓
WordPress dynamic generation
↓
1 vCPU again bears the pressure of cache warming
This is exactly the opposite of the optimization goal for this round.
So after making the modification, I chose to:
Keep the existing cache and let the new lifetime configuration take effect gradually during natural operation.
18. After Saving, a Yoast SEO Extension Prompt Appeared, Which I Also Temporarily Ignored
After saving the settings, a prompt appeared in the W3TC dashboard:
Activating the Yoast SEO extension for W3 Total Cache may be helpful for your site.
I did not enable it at this stage.
The reason was not that I thought there was a problem with this extension, but because I was currently observing:
Maximum lifetime
3600 → 43200
whether this single adjustment was effective.
If I continued to modify:
Yoast SEO Extension
Redis
PHP-FPM
Cache Preload
CDN
at the same time, then if the CPU alert changed the next time, it would be very difficult to determine which factor actually caused it.
So at this stage, I continued to follow:
Change only one variable at a time.
19. This Investigation Changed My Understanding of Several Issues
CPU Saturation Does Not Necessarily Require a Very High Request Volume
If the server only has 1 vCPU, and a single Cold WordPress page takes 10 seconds, then just a few concurrent dynamic requests are already dangerous enough.
Reaching the max_children Limit Does Not Mean It Should Be Raised
This time, PHP-FPM explicitly warned:
server reached max_children setting (7)
But there was only 1 vCPU.
Adding more workers does not magically produce more CPU.
A Slow Redis Slowlog Does Not Mean Redis Is Necessarily the Root Cause
Redis did indeed experience slow commands at the level of:
10ms
20ms
50ms
80ms
But the server only had one CPU core, and PHP-FPM was competing for that core at full capacity.
Redis might have just been slowed down by the overall system’s CPU saturation.
Redis might have just been slowed down by the overall system’s CPU saturation.
What Truly Matters Is Avoiding Entering PHP as Much as Possible
One W3TC HIT:
approx. 0.016 seconds
One Cold MISS:
10.6 seconds
This order-of-magnitude gap is far more important than continuing to tweak a few PHP parameters.
20. The Previous CDN Optimization Was Not Done in Vain
This investigation also gave me a clearer understanding of the previous day’s CDN optimization for detail page query strings.
The previous article solved:
Same article
+
Meaningless parameters
+
Insufficient cache reuse
It could reduce a portion of the requests reaching the origin server.
What was exposed this time, however, was a much broader scope:
/tag/
/category/
/page/
Feed
Search
REST API
Various historical articles
And a large number of different Cold URLs
So:
Detail page CDN optimization is an effective layer, but it alone cannot solve all CPU alerts.
If subsequent observations show a significant reduction in CPU alerts, it will mean that the two layers of CDN and W3TC optimization are starting to have a compounding effect.
21. Current Production Configuration
After this round, I ultimately only changed:
W3 Total Cache
Page Cache
Maximum lifetime
3600 seconds
↓
43200 seconds
Which is:
1 hour → 12 hours.
The following items were temporarily maintained:
Garbage Collection Interval: 3600 seconds
Cache Preload: Disabled
Cache Query String: Disabled
PHP-FPM max_children: 7
Redis Object Cache: Remains enabled
Yoast SEO W3TC Extension: Not enabled for now
At the same time:
Do not manually empty the existing Page Cache.
22. How to Judge if It Is Effective in the Next Stage
This time, I do not plan to continue making immediate modifications.
What is truly meaningful is letting:
43200 seconds
run in production for a while.
Subsequent focus will be on observing:
Whether CPU alerts decrease;
Whether the duration of single instances approaching 100% shortens;
Whether PHP-FPM still frequently:
reached max_children setting (7)
Whether Nginx will again show hundreds of 499s during the next alert period;
And whether the proportion of Cold MISSes gradually decreases as the Page Cache becomes increasingly warm.
If these metrics improve significantly, it means:
Extending the valid lifetime of already generated caches is the right direction.
If there is no significant effect, I will further consider:
CDN cache strategy for Tag / Category / Page
Access control for low-value crawlers
Why Cold pages take 10 seconds to generate
Dynamic generation cost of Gutenberg Navigation / taxonomy
rather than changing everything all at once right now.
Conclusion
This investigation was very interesting.
At first, I thought it was just an incomplete resolution of the query string cache issue from the previous article.
Then I went on to suspect:
Nginx
PHP-FPM
Gutenberg
Polylang
W3 Total Cache
Redis
Among them, Redis Slowlog did show a large number of abnormal latencies, and about 73% of the PHP Slowlog samples fell on W3TC Redis GET/MGET.
But what truly made me rethink the entire problem was:
nproc = 1
Combined with:
/page/9/
Cold MISS:
10.602379 seconds
Cache HIT:
0.016091 seconds
The answer finally became simple.
The truly scarce resource on this WordPress server right now is:
The single CPU core.
Therefore, rather than having more PHP-FPM workers working simultaneously, I prefer:
Requests not entering PHP at all.
The CDN, W3TC Disk Enhanced, and extending the Page Cache lifetime are all essentially doing the same thing:
Returning cached results directly for pages that have already been generated, as much as possible.
This time, I first increased the maximum lifetime of the W3TC Page Cache from:
1 hour
to:
12 hours.
As for whether it can actually reduce CPU alerts, I will leave it to production data to verify.
需要长期技术维护或远程问题排查?
我是拥有 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


发表回复