There is no problem not worth solving, and no technology not worth learning!

Troubleshooting Persistent EdgeOne 301 Requests After Moving WordPress Images to a Media Subdomain

【图3,EdgeOne 301 请求的 URL Path 排行,集中在 /wp-content/uploads/ 图片路径】

作者:

WordPress CDN & Global Acceleration Practice

图16:浏览器访问正常,网站全球访问成功

(1) Complete migration combat record from Alibaba Cloud DNS to Cloudflare global acceleration

这一套规则遵循 Cloudflare + WordPress 标准 CDN 架构:

(2) CloudFlare SSL/TLS and Cache Rules configuration combat

图12:评论实时生效

(3) WordPress + CloudFlare Recommended Configuration with Cache Rules Practical Optimization (SSL/API/Cached Closed Loop Full Record)

CDN上线后的boce数据

(4) Performance comparison before and after CDN launch: WordPress cache misjudgment correction and real acceleration effect verification

【图9:boce 测试 cn-test,灰云直连阿里云 ECS,平均响应 0.478s】

(5) Is Cloudflare Free Suitable for a Mainland China WordPress Site? A Real-World Test from Cache HIT to Gray-Cloud Direct Aliyun ECS

[Figure 1: Tencent Cloud EdgeOne product page, selecting “Start Now”]

(6) Migrating a WordPress Main Site from Cloudflare Free to Tencent Cloud EdgeOne: A Real-World CDN Acceleration Setup for China and Overseas Traffic

【图2:EdgeOne 上线后 boce 第二次测试,平均响应 0.354s,不可访问 2 个】

(7) How Much Faster Is EdgeOne After Migrating from Cloudflare Free? A Three-Stage WordPress CDN Test with boce and WebPageTest

[图2,EdgeOne 昨日 L7 访问流量,显示 6.56 GB]

(8) EdgeOne Traffic Cost Troubleshooting: Moving WordPress Uploads Static Assets to a Cloudflare Media Subdomain

【图3,EdgeOne 301 请求的 URL Path 排行,集中在 /wp-content/uploads/ 图片路径】

(9) Troubleshooting Persistent EdgeOne 301 Requests After Moving WordPress Images to a Media Subdomain

Recently, I moved my WordPress image assets to a dedicated media subdomain:

Plaintext
https://media.shuijingwanwq.com/

The goal was to reduce the static asset load on the main site, www.shuijingwanwq.com, and let image files be handled through a separate CDN path.

After the migration, newly written posts were already using image URLs under media.shuijingwanwq.com correctly. However, in EdgeOne access analytics, I noticed something odd: several hours after switching to the media subdomain, there were still quite a few 301 requests in the most recent one-hour window.

At first, I thought this might be caused by EdgeOne cache, browser cache, or leftover crawler requests from search engines. After checking further, I confirmed that the main cause was old image URLs hardcoded in historical post content.

[Figure 1: In the EdgeOne status code ranking for the most recent one hour, 301 still accounts for 5.59 MB, about 2.93%]
[Figure 1: In the EdgeOne status code ranking for the most recent one hour, 301 still accounts for 5.59 MB, about 2.93%]

1. The symptom: EdgeOne still showed 301 responses in the most recent one hour

In EdgeOne metric analysis, the latest one-hour data looked roughly like this:

Plaintext
200:183.2 MB,占 96.16%
301:5.59 MB,占 2.93%
404:1.55 MB,占 0.81%

Judging only by the percentage, 301 responses were not extremely high. But since the image migration had already been completed for several hours, the ideal behavior was that newly served pages should directly output the media subdomain instead of frequently requesting the old /wp-content/uploads/ URLs.

So I filtered the status code further:

Plaintext
边缘响应状态码 = 301

After applying the filter, the 301 requests alone in the most recent one-hour window were:

Plaintext
L7 访问流量:7.25 MB
L7 访问请求数:966 次
[Figure 2: EdgeOne data after filtering by status code 301]
[Figure 2: EdgeOne data after filtering by status code 301]

Then I checked the URL Path ranking and found that many 301 responses were concentrated on old image paths, for example:

Plaintext
/wp-content/uploads/2026/07/21-2-1024x175.png
/wp-content/uploads/2026/07/23-2-1024x180.png
/wp-content/uploads/2018/04/14-3-520x245.png
/wp-content/uploads/2023/12/3-6.png
/wp-content/uploads/2021/08/5-3.png
[Figure 3: EdgeOne 301 URL Path ranking, concentrated on /wp-content/uploads/ image paths]
[Figure 3: EdgeOne 301 URL Path ranking, concentrated on /wp-content/uploads/ image paths]

This showed that although the image files had been moved to the media subdomain, many requests were still hitting the old image URLs.

2. First, confirm whether the 301 requests were going back to the origin

After seeing the 301 responses, the first key question was:

Were these 301 responses handled directly by the EdgeOne edge nodes, or were they being forwarded back to WordPress / Nginx every time?

If every request went back to the origin, the origin server would still be under pressure.
If the edge node returned the 301 directly, the issue would be much less serious.

I tested one of the old image URLs with curl:

Bash
curl -I https://www.shuijingwanwq.com/wp-content/uploads/2026/07/21-2-1024x175.png

The important parts of the response were:

Plaintext
HTTP/2 301
location: https://media.shuijingwanwq.com/2026/07/21-2-1024x175.png
content-length: 0
server: TencentEdgeOne
x-cache-lookup: Return Directly

This result means:

The 301 response for the old image URL was returned directly by the EdgeOne edge node and did not go back to WordPress.

In particular:

Plaintext
x-cache-lookup: Return Directly

This was the key signal. It indicates that EdgeOne returned the redirect response directly instead of forwarding the request to the origin and then returning the result.

So even though this group of 301 responses still appeared in EdgeOne analytics, it was not an origin-server pressure problem by itself.

3. Then confirm whether images on the media subdomain were being cached properly

Next, I tested the redirected media image URL:

Bash
curl -I https://media.shuijingwanwq.com/2026/07/21-2-1024x175.png

The first request returned:

Plaintext
HTTP/2 200
server: cloudflare
cache-control: max-age=2592000
cf-cache-status: MISS

After requesting it again, the second response showed:

Plaintext
HTTP/2 200
server: cloudflare
age: 10
cf-cache-status: HIT

This confirmed that image assets under the media subdomain could be cached by Cloudflare correctly.

So the request flow at that point could be understood as:

Plaintext
旧图片地址:
https://www.shuijingwanwq.com/wp-content/uploads/...

EdgeOne 边缘直接 301:
location: https://media.shuijingwanwq.com/...

新图片地址:
https://media.shuijingwanwq.com/...

Cloudflare:
第一次 MISS,第二次 HIT

In other words, the image file itself was not repeatedly hitting the origin, and caching on the media subdomain was working normally.

4. The real issue: old posts were still outputting old image URLs in their HTML

Since the EdgeOne 301 responses were handled directly at the edge, and the media image cache was also working, the remaining question was:

Why were there still so many requests to old image URLs?

I first checked the homepage HTML:

Bash
curl -s https://www.shuijingwanwq.com/ | grep -o "/wp-content/uploads/[^\"') ]*" | head

The homepage produced no output, which meant it was basically clean.

Then I checked an older post:

Bash
curl -s https://www.shuijingwanwq.com/2021/08/04/5122/ | grep -o "/wp-content/uploads/[^\"') ]*" | head

The command returned many old paths:

Plaintext
/wp-content/uploads/2021/08/1-2.png
/wp-content/uploads/2021/08/1-2.png
/wp-content/uploads/2021/08/2-2.png
/wp-content/uploads/2021/08/2-2.png
/wp-content/uploads/2021/08/3-1.png

This confirmed that the issue was not just historical cache. The old post page itself was still outputting old image URLs.

I also viewed the source code of that old post in the browser and found many occurrences of:

Plaintext
/wp-content/uploads/
[Figure 4: /wp-content/uploads/ could still be found in the page source of the old post]
[Figure 4: /wp-content/uploads/ could still be found in the page source of the old post]

This explained why EdgeOne still had 301 responses:

Plaintext
用户或爬虫访问旧文章

旧文章 HTML 中仍然包含 /wp-content/uploads/ 图片地址

浏览器继续请求旧图片地址

EdgeOne 边缘返回 301

跳转到 media.shuijingwanwq.com

5. Why did new posts work correctly while old posts did not?

At first, I wondered whether this was related to differences between the Classic Editor and Gutenberg.

After checking, I found that it had nothing to do with the editor type.

The root cause was:

Image URLs inside WordPress post content are essentially HTML strings stored in wp_posts.post_content.

For images inserted before the migration, the post content stored URLs like this:

HTML
<img src="https://www.shuijingwanwq.com/wp-content/uploads/2021/08/1-2.png">

For images uploaded and inserted after the migration, the post content stored URLs like this:

HTML
<img src="https://media.shuijingwanwq.com/2026/07/1-22.png">

So the media configuration only affects newly generated image URLs after the migration. It does not automatically go back and rewrite the HTML in historical posts.

[Figure 5: In the old post code editor before the migration, the image URL was still www.shuijingwanwq.com/wp-content/uploads]
[Figure 5: In the old post code editor before the migration, the image URL was still www.shuijingwanwq.com/wp-content/uploads]
[Figure 6: In the new post code editor after the migration, the image URL had already become media.shuijingwanwq.com]
[Figure 6: In the new post code editor after the migration, the image URL had already become media.shuijingwanwq.com]

6. Use Better Search Replace for a dry run first

Because Better Search Replace was already installed on my site, I did not install WP-CLI separately. I used the plugin directly to check the data first.

At first, I searched for:

Plaintext
Search for:
/wp-content/uploads/

Replace with:
https://media.shuijingwanwq.com/

I selected only:

Plaintext
wp_posts

And enabled:

Plaintext
Run as dry run

The dry run showed:

Plaintext
6455 cells were found that need to be updated
[Figure 7: Better Search Replace dry run for /wp-content/uploads/ showing 6,455 cells]
[Figure 7: Better Search Replace dry run for /wp-content/uploads/ showing 6,455 cells]

However, this search condition was too broad and was not suitable for direct replacement.

There were two types of content in old posts.

The first type was a full image URL:

Plaintext
https://www.shuijingwanwq.com/wp-content/uploads/2021/08/1-2.png

This type could be safely replaced with:

Plaintext
https://media.shuijingwanwq.com/2021/08/1-2.png

The second type was technical explanation, path documentation, or server directory notes inside the article body, for example:

Plaintext
/wp-content/uploads/2021/07/

This might not be an image loading URL at all. It could simply be part of the article content. Replacing it directly could damage the original troubleshooting context.

So I should not blindly replace every occurrence of /wp-content/uploads/.

7. Use the full old image domain for the actual replacement

A safer search condition was the complete old image URL prefix:

Plaintext
Search for:
https://www.shuijingwanwq.com/wp-content/uploads/

Replace with:
https://media.shuijingwanwq.com/

I continued to select only:

Plaintext
wp_posts

And first enabled:

Plaintext
Run as dry run

This dry run showed:

Plaintext
3304 cells were found that need to be updated
[Figure 8: Better Search Replace dry run using the full old image domain, with matches reduced to 3,304 cells]
[Figure 8: Better Search Replace dry run using the full old image domain, with matches reduced to 3,304 cells]

This result was much closer to what I expected.

Because it only targeted complete old image URLs explicitly hardcoded in historical posts:

Plaintext
https://www.shuijingwanwq.com/wp-content/uploads/

It would not accidentally modify relative paths recorded in normal article content:

Plaintext
/wp-content/uploads/

Before running the real replacement, I created a database backup with UpdraftPlus. After confirming that the database backup was complete, I disabled Run as dry run and executed the actual replacement.

The replacement result was:

Plaintext
During the search/replace, 1 tables were searched, with 3304 cells changed in 3299 updates.
[Figure 9: Better Search Replace completed successfully, with 3,304 cells changed in 3,299 updates]
[Figure 9: Better Search Replace completed successfully, with 3,304 cells changed in 3,299 updates]

8. Clear W3 Total Cache and verify again

After the actual replacement finished, I first checked the code editor of the old post and found that the image URL had changed to:

Plaintext
https://media.shuijingwanwq.com/2021/08/1-2.png
[Figure 10: In the old post code editor, the image URL had been replaced from www to media]
[Figure 10: In the old post code editor, the image URL had been replaced from www to media]

However, when I initially used curl to access the frontend page, I could still see the old URL.

This meant the database replacement had succeeded, but the frontend page cache had not been refreshed yet.

So I cleared W3 Total Cache in the WordPress dashboard:

Plaintext
Performance → Purge All Caches

After clearing the cache, I ran the check again:

Bash
curl -s "https://www.shuijingwanwq.com/2021/08/04/5122/?_nocache=$(date +%s)" | grep -o "https://www.shuijingwanwq.com/wp-content/uploads/[^\"') ]*" | head

This time there was no output.

Then I checked the relative path pattern:

Bash
curl -s "https://www.shuijingwanwq.com/2021/08/04/5122/?_nocache=$(date +%s)" | grep -o "/wp-content/uploads/[^\"') ]*" | head

There was no output either.

This confirmed that the frontend HTML of this old post was no longer outputting the old image URL.

9. Why I did not continue replacing the remaining /wp-content/uploads/ occurrences

After replacing the first batch of complete old image URLs, I ran another Better Search Replace dry run:

Plaintext
Search for:
/wp-content/uploads/

Replace with:
https://media.shuijingwanwq.com/

The result showed that the remaining count had dropped significantly.

[Figure 11: After replacing complete URLs, another dry run checked the remaining /wp-content/uploads/ occurrences]
[Figure 11: After replacing complete URLs, another dry run checked the remaining /wp-content/uploads/ occurrences]

But I did not run the actual replacement for those remaining matches.

The reason was that read-only checks showed many remaining /wp-content/uploads/ occurrences were not image loading URLs. They were technical notes in article content, attachment titles, historical revisions, or server paths recorded during earlier troubleshooting.

For example, some articles were discussing things like:

Plaintext
通过 FlashFXP 上传文件至 /wp-content/uploads/2021/07/

If this kind of content were forcefully replaced with:

Plaintext
https://media.shuijingwanwq.com/2021/07/

it would change the meaning of the article.

So my rule was:

Plaintext
完整旧图片 URL 可以批量替换;
单独的 /wp-content/uploads/ 不再无脑替换。

10. Conclusion from this troubleshooting process

The core problem this time was not an abnormal EdgeOne 301 issue, nor was it a failed cache setup for the media subdomain. The real issue was:

After moving to a media subdomain, historical image URLs saved inside old WordPress post content do not update automatically.

The final handling logic was:

Plaintext
1. EdgeOne 仍然有 301
2. 筛选发现 301 集中在 /wp-content/uploads/ 图片路径
3. curl 确认 301 是 EdgeOne 边缘直接返回,没有回源
4. curl 确认 media 子域名图片可以正常 Cloudflare HIT
5. 检查旧文章 HTML,发现仍然输出旧图片地址
6. 用 Better Search Replace dry run 检查完整旧图片 URL
7. 备份数据库
8. 正式替换完整旧图片 URL
9. 清理 W3 Total Cache
10. 重新验证旧文章前台 HTML 已经不再输出旧图片地址

The URL that really needed to be replaced was:

Plaintext
https://www.shuijingwanwq.com/wp-content/uploads/

It was replaced with:

Plaintext
https://media.shuijingwanwq.com/

Instead of directly replacing every occurrence of:

Plaintext
/wp-content/uploads/

across the database.

11. Follow-up observation

After the replacement was completed, the 301 count in EdgeOne would not immediately drop to zero.

Possible reasons include:

Plaintext
搜索引擎仍然可能请求旧图片 URL;
浏览器缓存中可能还保存旧地址;
外部引用可能仍然访问旧图片路径;
EdgeOne 的统计数据也有时间窗口延迟。

But when real users access old posts later, the page HTML now directly outputs image URLs under the media subdomain. It no longer asks the browser to request the old /wp-content/uploads/ path first, so the 301 responses triggered by article pages should gradually decrease.

For me, this troubleshooting process was another reminder:

WordPress media URL settings affect images inserted in the future, but they do not automatically rewrite historical post content.

If a site has been running for a long time and has many historical posts, fixed URLs inside old post content should always be included in the checklist when migrating image domains, CDN domains, or static asset domains.

EdgeOne Traffic Cost Troubleshooting: Moving WordPress Uploads Static Assets to a Cloudflare Media Subdomain

Need long-term technical maintenance or remote troubleshooting?

I am a PHP / Go backend engineer with 15+ years of experience, focused on existing system maintenance, bug fixing, performance optimization, server troubleshooting, WordPress maintenance, and small feature iterations.

If your project is facing any of the following issues, we can start with a small troubleshooting task first:

  • ✅ PHP / Laravel / Yii2 legacy systems without active maintenance
  • ✅ Go / Gin backend APIs that need troubleshooting or optimization
  • ✅ Slow, broken, or unstable WordPress websites
  • ✅ Nginx / MySQL / Redis / Linux server issues
  • ✅ CDN / Cloudflare / DNS / HTTPS configuration problems
  • ✅ Long-term remote technical support or part-time maintenance

More details: About Me & Collaboration

WeChat: 13980074657
Email: shuijingwanwq@gmail.com
Telegram: @shuijingwan
GitHub: https://github.com/shuijingwan

评论

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.