客户反馈域名跳转不生效,我却一测就是 301。查到最后发现:跳转确实是”生效”了,但生效的原因是个乌龙。
下文域名与 IP 均为示意:old.com 是准备跳转的旧域名,new.com 是新域名,203.0.113.10 表示实际抓到的 Cloudflare 边缘 IP。真实排查时请替换成自己的域名和实测 IP。
现象:客户说跳不动,我这边却一跳一个准
客户在 Cloudflare 上给旧域名 old.com 配了 301 跳转,目标是新域名 new.com。客户反馈:在国内访问 old.com,页面根本不会跳转。
我这边连上代理一测,curl 稳稳返回 301。
第一个怀疑是”屏蔽了国内访问”——是不是旧域名上有针对中国大陆的访问规则?检查后发现确实有类似配置,于是先把它删掉,再让客户试,依然不行。
问题就变成了:为什么我开代理能跳,客户在国内不能跳?
排查:先看 DNS,而不是急着看规则
跳转要生效,前提是访问者能先”连上 Cloudflare”。如果域名解析都失败,规则写得再好也没机会执行。于是先查 old.com 的解析:
dig @1.1.1.1 old.com. A
dig @1.1.1.1 www.old.com. A
dig @1.1.1.1 old.com. CNAME
结果很干净:根域名 @ 和 www 的 A、AAAA、CNAME 一条都没有。也就是说,这个域名在公网上根本没有可用地址。顺手用 httpstatus.io、redirect-checker.org 测,结果同样测不出跳转——它们也解析不到这个域名。
到这里矛盾出现了:解析都不存在,理论上任何人都不该跳转成功,可我的代理就是能拿到 301。
用 curl 复现:同一套规则,两种结果
先确认代理上的 301 是真的,而不是浏览器缓存。用 curl 只看响应头,并把连接 IP、状态码、跳转目标一起打出来:
curl -sS -D - -o /dev/null \
--connect-timeout 10 --max-time 20 \
-w '\n连接 IP: %{remote_ip}\n状态码: %{http_code}\n跳转目标: %{redirect_url}\n' \
'https://old.com/'
输出大致是这样(IP 是示意,实际值来自上面 curl 的 remote_ip):
HTTP/1.1 301 Moved Permanently
Location: https://new.com/
Server: cloudflare
CF-RAY: 8f5ea2...-NRT
连接 IP: 203.0.113.10
状态码: 301
跳转目标: https://new.com/
关键信息是 %{remote_ip}——它告诉我们 curl 真正连到了哪个 Cloudflare 边缘 IP,后面要用它做对照实验。
接着在国内测试机上复现失败场景。Windows 下用 curl.exe(避开 PowerShell 的别名),先关掉代理软件的 TUN/全局代理,不指定任何 IP 直接请求:
curl.exe --noproxy "*" --connect-timeout 10 --max-time 20 -v -o NUL \
-w "\nIP: %{remote_ip}\nHTTP: %{http_code}\n" "https://old.com/"
结果连 DNS 阶段都没过去:
* Could not resolve host: old.com
curl: (6) Could not resolve host: old.com
同样的域名、同样的规则,这次用 --resolve 把 old.com 强行指到刚才那个 Cloudflare 边缘 IP 再试。注意 --resolve 只改连接地址,URL、TLS 的 SNI 和 HTTP 的 Host 仍然是 old.com:
curl.exe --noproxy "*" --resolve "old.com:443:203.0.113.10" \
--connect-timeout 10 --max-time 20 -v -o NUL \
-w "\nIP: %{remote_ip}\nHTTP: %{http_code}\n" "https://old.com/"
这次就正常返回了 301,Location 指向 new.com。
这个对照组把问题圈死了:
- Cloudflare 的跳转规则一直是好的,谁的请求只要带着
old.com到达边缘节点,都会返回 301,没有针对国内网络的区别对待; - 国内访问失败不是 IP 被墙——同一个边缘 IP 让客户直接 ping 也能通;
- 唯一的差别是解析:国内机器解析
old.com失败,连 Cloudflare 都到不了,自然谈不上跳转。
可这样一来新的矛盾又出现了:代理机器的 DNS 配置看着也正常,它凭什么能解析到那个 IP?
转折:ping 的时候,域名自己多了一个后缀
在代理服务器上 ping 旧域名,注意看输出:
$ ping old.com
PING old.com.com (203.0.113.10) 56(84) bytes of data.
它解析的根本不是 old.com,而是 old.com.com——域名后面被偷偷追加了一个 .com。而且 old.com.com 恰好也是 Cloudflare 的服务,返回的正是之前 curl 连上的那个 IP。
curl 拿到这个 IP 去建连时,请求里带的域名仍然是 old.com(SNI 和 Host 都没变)。Cloudflare 收到对 old.com 的请求,一看规则存在,直接回了 301。
所以跳转”生效”了——生效得莫名其妙。
根因:glibc 拿 hostname 猜了一个搜索域
为什么代理服务器会去解析 old.com.com?看它的 DNS 配置:
$ cat /etc/resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4
只有 nameserver,既没有 search 也没有 domain。这种情况下,Linux 的 glibc 解析器会自己做主:当没有配置搜索列表时,从本机 hostname 第一个点之后的部分推导默认搜索域。
代理服务器的主机名大概是这个画风:
$ hostname
vps.com
于是默认搜索域被推导成 .com。解析 old.com 失败后,解析器继续尝试 old.com 拼接上 .com,也就是 old.com.com——这个域名有解析,还指向 Cloudflare。整条链路就这么通了。
用这个机制回头看,所有现象都说得通:
- 我(代理)能跳:不是 Cloudflare 在兜底,是代理机器的 DNS 走了条”邪路”,碰巧撞上一个能用的 IP;
- 客户在国内不能跳:国内网络老老实实解析
old.com,解析失败就是连不上,自然拿不到 301; - 跟”屏蔽国内”没关系:规则删了也一样,真正的瓶颈是旧域名压根没有解析。
验证与修复
想确认是不是搜索域在捣鬼,对比带不带尾点(尾点表示绝对域名,不追加搜索后缀)的解析结果:
getent ahostsv4 old.com # 可能能解析出来:被追加了后缀
getent ahostsv4 old.com. # 解析失败:不带后缀才是真实情况
也可以顺着 hostname 把来源查清楚:
hostname
cat /etc/hostname
cat /etc/resolv.conf
修复方向很简单:让旧域名在公网上真正可解析。如果它只用来做跳转,可以按 Cloudflare 官方做法,给 @ 和 www 各加一条 A 记录 192.0.2.1 并开启代理(橙云)。这样访问者能正常解析到 Cloudflare 边缘节点,跳转规则才有机会执行。
总结
这次排查给我留下几个印象:
- “我这边能通”不等于”配置没问题”。本地解析路径可能骗人,你连上的未必是你以为的那个服务器。
- 排查跳转问题先查 DNS。解析都失败了,就别急着怀疑规则写错、网络被墙。
- curl 的
-w参数记得用。%{remote_ip}、%{http_code}、%{redirect_url}一次打全,能省很多来回确认的功夫。 - 看到”能通但不能通”的怪现象,多用
dig、getent、ping看实际解析结果,域名输出和输入对不上,往往是关键线索。