Recently, I moved my WordPress image assets to a dedicated media subdomain:
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%]](https://media.shuijingwanwq.com/2026/07/1-23-1024x388.png)
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:
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:
边缘响应状态码 = 301
After applying the filter, the 301 requests alone in the most recent one-hour window were:
L7 访问流量:7.25 MB
L7 访问请求数:966 次
![[Figure 2: EdgeOne data after filtering by status code 301]](https://media.shuijingwanwq.com/2026/07/2-22-1024x412.png)
Then I checked the URL Path ranking and found that many 301 responses were concentrated on old image paths, for example:
/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]](https://media.shuijingwanwq.com/2026/07/3-21.png)
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:
curl -I https://www.shuijingwanwq.com/wp-content/uploads/2026/07/21-2-1024x175.png
The important parts of the response were:
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:
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:
curl -I https://media.shuijingwanwq.com/2026/07/21-2-1024x175.png
The first request returned:
HTTP/2 200
server: cloudflare
cache-control: max-age=2592000
cf-cache-status: MISS
After requesting it again, the second response showed:
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:
旧图片地址:
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:
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:
curl -s https://www.shuijingwanwq.com/2021/08/04/5122/ | grep -o "/wp-content/uploads/[^\"') ]*" | head
The command returned many old paths:
/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:
/wp-content/uploads/
![[Figure 4: /wp-content/uploads/ could still be found in the page source of the old post]](https://media.shuijingwanwq.com/2026/07/4-21-1024x241.png)
This explained why EdgeOne still had 301 responses:
用户或爬虫访问旧文章
↓
旧文章 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:
<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:
<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]](https://media.shuijingwanwq.com/2026/07/5-17-1024x423.png)
![[Figure 6: In the new post code editor after the migration, the image URL had already become media.shuijingwanwq.com]](https://media.shuijingwanwq.com/2026/07/6-15-1024x578.png)
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:
Search for:
/wp-content/uploads/
Replace with:
https://media.shuijingwanwq.com/
I selected only:
wp_posts
And enabled:
Run as dry run
The dry run showed:
6455 cells were found that need to be updated
![[Figure 7: Better Search Replace dry run for /wp-content/uploads/ showing 6,455 cells]](https://media.shuijingwanwq.com/2026/07/7-8.png)
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:
https://www.shuijingwanwq.com/wp-content/uploads/2021/08/1-2.png
This type could be safely replaced with:
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:
/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:
Search for:
https://www.shuijingwanwq.com/wp-content/uploads/
Replace with:
https://media.shuijingwanwq.com/
I continued to select only:
wp_posts
And first enabled:
Run as dry run
This dry run showed:
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]](https://media.shuijingwanwq.com/2026/07/8-7.png)
This result was much closer to what I expected.
Because it only targeted complete old image URLs explicitly hardcoded in historical posts:
https://www.shuijingwanwq.com/wp-content/uploads/
It would not accidentally modify relative paths recorded in normal article content:
/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:
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]](https://media.shuijingwanwq.com/2026/07/9-6.png)
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:
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]](https://media.shuijingwanwq.com/2026/07/10-7-1024x422.png)
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:
Performance → Purge All Caches
After clearing the cache, I ran the check again:
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:
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:
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]](https://media.shuijingwanwq.com/2026/07/11-5.png)
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:
通过 FlashFXP 上传文件至 /wp-content/uploads/2021/07/
If this kind of content were forcefully replaced with:
https://media.shuijingwanwq.com/2021/07/
it would change the meaning of the article.
So my rule was:
完整旧图片 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:
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:
https://www.shuijingwanwq.com/wp-content/uploads/
It was replaced with:
https://media.shuijingwanwq.com/
Instead of directly replacing every occurrence of:
/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:
搜索引擎仍然可能请求旧图片 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.
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