〇、前言
我有个服务跑在局域网内,对外通过 Cloudflare 代理暴露,对内监听 9443 端口。理想状态是:在家直连内网 IP,速度快;出门走 Cloudflare,能访问就行。但实际折腾下来,发现“让同一个域名在家走内网、在外走公网”这件事,远比想象中琐碎。
问题出在三个层面:Cloudflare 的 IPv6 优先策略会覆盖本地 hosts 的 IPv4 解析;端口不一致导致内外网访问表现割裂;路由器上配了 hosts 却被 Clash 的 DNS 接管架空。本文记录整个排查与解决过程,顺带补一些当时没想明白的知识点。
一、关掉 Cloudflare 的 IPv6,让本地 IPv4 解析有机会生效
最初的本地方案很朴素:在本机 hosts 文件里把域名指向内网 IP,期望浏览器直连局域网服务器。但实测发现,浏览器有时根本不走这条记录——它会优先用 Cloudflare 返回的 IPv6 地址去连接,而那个地址绕一圈又回到了公网路径,本地 IPv4 映射形同虚设。
原因在于 Cloudflare 默认对所有域名开启 IPv6 Compatibility,会自动为代理记录生成 AAAA 记录。当客户端同时拿到 A(IPv4)和 AAAA(IPv6)时,现代浏览器和操作系统通常优先尝试 IPv6。只要 IPv6 路径可达(哪怕绕远路),就不会回退到本地 hosts 里的 IPv4 地址。
问题在于,Cloudflare 的控制台并不允许免费版用户直接关闭 IPv6。根据官方文档,只有 Enterprise 账户才能在 Dashboard 的 Network 页面里把 IPv6 Compatibility 开关关掉;免费版用户被告知应改用 Pseudo IPv4,但那只是把 IPv6 地址哈希成一个假的 IPv4 展示出来,并不改变实际连接行为。
实际走通的路子是调 API。Cloudflare 的 Zone Settings API 接受一个 PATCH 请求,把 ipv6 设置为 "off":
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/settings/ipv6" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"value":"off"}'Token 需要 Zone Settings: Edit 权限,Zone ID 在域名概述页右侧可以找到。这个操作在免费账户上目前仍然有效,虽然官方文档没有把它列为免费版的功能。
如果不想自己创建 Token、拼 curl 命令,Cloudflare Dashboard 里内置的 AI 助手也能代劳。直接对它说“帮我通过 API 关闭这个站点的 IPv6”,它会自动完成权限校验和请求发送。这比手动走一遍 Token 创建流程要省事得多。
补充一点:关掉 IPv6 Compatibility 之后,Cloudflare 不再为你的域名生成 AAAA 记录,但这并不意味着 IPv6 流量完全消失。Tor 网络仍然可能通过 IPv6 进来。如果确实需要彻底阻断,可以再加一条 WAF 自定义规则,拦截源 IP 在 ::/0 范围内的请求。不过对个人站点来说,这一步通常不是必须的。
二、用 iptables 把 443 流量转到 9443,统一内外网端口
关掉 IPv6 后,本地 hosts 的 IPv4 记录终于能生效了。但紧接着暴露了另一个问题:内外网端口不一致。
服务本身监听的是 9443,局域网内直接访问域名时,浏览器默认走 443,打不开。在外网时,Cloudflare 代理会帮你把 443 的请求转发到源站的 9443,所以用户感知不到。但一进内网,Cloudflare 的代理链路被绕过443 端口没人监听,访问就断了。
解决方案是用 iptables 做本地端口重定向,把进入本机 443 的 TCP 流量在 NAT 表里直接改写到 9443:
# 把进入本机 443 端口的 TCP 流量重定向到 9443
sudo iptables -t nat -I PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 9443
# 放行 9443 端口(如果已有规则可跳过)
sudo iptables -I INPUT -p tcp --dport 9443 -j ACCEPT这样无论内网还是外网,用户始终访问 https://域名(即 443),内网流量在本机被静默转到 9443,外网流量由 Cloudflare 转发到源站的 9443,表现完全一致。
需要注意REDIRECT 目标只适用于本机入站流量,且写的是 PREROUTING 链。如果服务跑在 Docker 容器里、或者经过其他网络命名空间,流量可能不经过宿主机的 PREROUTING,这条规则就会失效。那种场景下需要改用在 DOCKER 链或 OUTPUT 链上做处理。
三、路由器 hosts 被 Clash 架空:DNS 到底谁说了算
端口问题解决后,剩下最后一块拼图:让内网设备自动把域名解析到内网 IP,而不是靠每台机器手动改 hosts。
我的思路是在路由器(老毛子固件)的 DHCP 服务里添加 hosts 记录:内部网络 → DHCP 服务 → 编辑 hosts,把域名指向内网服务器的 192.168.123.x。预期是:内网设备向路由器问 DNS 时,直接拿到内网 IP;出门后路由器不在链路里,自然走公网解析。
但配置完发现,内网访问依然走了外网 IP。
3.1 老毛子固件自带的三个 DNS 要先删掉
排查路由器这一侧时,先发现了一个容易被忽略的细节:**老毛子固件在 DHCP 服务里默认预置了三个 DNS 服务器地址**(通常是运营商 DNS 或公共 DNS,比如 114.114.114.114223.5.5.5 之类)。这几个默认项如果不删,路由器下发 DNS 时会优先从这三台服务器里挑选,而不是把路由器自身当作 DNS 服务器交给客户端。
换句话说,即使你在 hosts 里配了记录,客户端拿到的 DNS 服务器根本不是路由器,那条 hosts 记录自然解析不到。必须把这**三个默认 DNS 全部删除**,让路由器自己接管 DNS 下发(即 DHCP 下发的 DNS 指向路由器自身),本机 hosts 才有机会生效。
操作路径:内部网络 → DHCP 服务 → 把 DNS 服务器列表里预置的三项逐一删掉,保存后重新让客户端获取 DHCP(或手动 ipconfig /renew / dhclient)。这一步是路由器 hosts 能起作用的前提,比后面 Clash 的配置更靠前。
3.2 Clash 的 DNS 接管
路由器侧理顺之后,还有第二层拦截:本机开着 Clash Verge,它的 DNS 模块会接管域名解析请求。当 Clash 处于 TUN 模式或启用了 DNS 劫持时,应用发出的 DNS 查询并不会到达路由器的 dnsmasq,而是由 Clash 内核自己按配置的 nameserver 去向上游查询。路由器上那条 hosts 记录,Clash 根本看不到。
要解决这个问题,需要在 Clash 的 DNS 配置里为特定域名指定解析策略。Mihomo(Clash Meta 内核)支持 nameserver-policy,可以按域名匹配并指定专用 DNS 上游:
dns:
nameserver-policy:
'site.example.com':
- 'dhcp://en0'dhcp://en0 的意思是:使用 en0 网卡通过 DHCP 获取到的 DNS 服务器来解析这个域名。在家时en0 拿到的 DNS 就是路由器,路由器上的 hosts 记录得以生效;离家后en0 拿到的 DNS 是当地网络下发的,域名会正常解析到公网 IP。
en0 是 macOS 的 Wi-Fi 接口名,Windows 用户需要换成对应的接口名(如 以太网 或 WLAN),Linux 下通常是 eth0 或 wlan0。如果不确定,可以在 Clash 的日志里看到接口信息,或者用 ipconfig / ifconfig 确认。
另一个可能踩的坑:排查过程中发现,某些情况下 DNS 被篡改成了 114.114.114.114。如果关闭 Clash 后域名仍然解析到错误的外网 IP,需要检查系统 DNS 设置是否被其他软件或网络配置覆盖。另外,Tailscale 的 Use Tailscale DNS settings 选项也会接管 DNS,如果不依赖 Tailscale 的设备名访问,可以关掉它,避免多一层干扰。
四、总结
整个方案的核心逻辑其实就三句话:
1. 关 IPv6:让 Cloudflare 不再下发 AAAA 记录,本地 hosts 的 IPv4 映射才有机会被使用。免费版只能走 API,AI 助手是捷径。
2. 转端口:iptables 把 443 重定向到 9443,内外网访问同一个 URL,体验一致。
3. 管住 DNS 链路:路由器 hosts 要生效,先删掉老毛子固件预置的三个默认 DNS,让路由器自己接管下发;再用 Clash 的 nameserver-policy 让特定域名走 dhcp:// 接口,在家问路由器,在外问当地网络。
回头看,每个环节单独看都不复杂,但串在一起时,**谁先解析、谁接管了 DNS、流量在哪个环节被改写**,这些问题的答案并不直观。老毛子固件预置 DNS 和 Clash 的 TUN 模式/DNS 劫持是尤其隐蔽的两层——它们在你以为“网络是通的”的时候,悄悄换掉了域名解析的出口。把这两层都理清楚之后,内外网分流才算真正稳定下来。