要把家里的一台机器暴露到公网,第一个问题是「入站到底通不通」。这个问题很容易被自己骗过去——本机扫一遍,端口全开,看起来一切正常;实际从外面根本连不上。
这篇记录一次完整的实测:先说为什么本机探测的结论必然是错的,再说这条线上实际测出来的入站情况,最后是几个只有真正从外面打回来才会发现的坑。
本机探测为什么必然错
在家里的机器上跑端口扫描,或者在同一台 Mac 上 nc -zv 自己,得到的结果和公网访客看到的完全是两回事。
直接原因是本地的透明代理。像 Clash 这类工具开启 TUN 模式后会接管整个网络栈,任何 TCP 连接尝试都先落到它手上。它对几乎所有目标地址都会先接受连接再决定怎么转发,于是每一个端口看起来都是开的——包括那些压根没有进程监听的端口。
即使没有代理,从内网连自己的公网 IP 也不可靠:多数家用路由器不支持回环 NAT(hairpin NAT),这条路径要么走不通,要么被路由器内部短路,两种结果都不代表外部访客的真实体验。
所以唯一可信的做法是从一台真正在公网上的机器打回来。有一台云服务器就够了:
ssh cloud-host "nc -zv -w 5 $HOME_IP $PORT"
没有对照端口的读数一律作废
只从外面测还不够。远端命令本身也可能因为别的原因失败——中间设备丢包、云厂商出站策略、命令写错、机器本身网络异常。这时候「连不上」会被误读成「端口被封」。
解决办法是每次探测都带一个你确信应该关闭的端口。比如同时测 22 和一个随手挑的高位端口:
for p in 22 443 8443 9999; do
ssh cloud-host "nc -zv -w 5 $HOME_IP $p" 2>&1 | tail -1
done
判据很简单:如果对照端口也「通」,这次读数作废——说明中间有东西在无差别接受连接,你测的不是真实的端口状态。反过来,如果对照端口正确地超时,而某个目标端口也超时,那才是有意义的「不通」。
这条规则我这次用上了两次,两次都拦下了错误结论。
第一步:先确认不是 CGNAT
如果运营商给的是 CGNAT 地址,后面所有端口讨论都没有意义——你根本没有一个可以被访问到的公网地址,只能靠打洞或中转。
判据是地址是否落在 100.64.0.0/10。这个段位是 RFC 6598 专门留给运营商级 NAT 的,看到它基本可以断定。
这条线上测出来的是一个正常的公网 IPv4 地址,不在那个段位里,从云端 ping 也通,往返约 155 毫秒。这意味着走 DDNS 挂 A 记录是可行的方案,不需要中转。
查自己公网 IP 有个小坑:常用的回显服务在这条线上直接失败,而另一些可以。写探测脚本时要准备多个来源并逐个回退,不要依赖单一服务——它挂掉的时候你会以为是自己网络的问题。
第二步:端口逐个测,并区分「被封」和「没开」
实测结果里最关键的一条:80 和 443 都不通,但这不是「没开」,是被运营商过滤了。
怎么区分这两者?就靠对照端口。如果 80 不通而 22 通,说明机器本身在线、转发规则也生效,问题出在特定端口上;如果连对照端口也不通,那是整条路不通,跟端口无关。
这条线上 22 是通的,一个空闲高位端口也是通的,唯独 80 和 443 静默丢弃——典型的家宽 HTTP 端口封锁,很多地区的运营商对住宅宽带都这么做,防止用户架站。
另一个发现是 8443 已经被占用——那上面跑着一个二次验证网关。这个信息只有真正探测才知道,凭记忆或凭文档都容易记错。
所以这条线上要暴露 HTTPS 服务,可用的是别的高位端口,80、443 和 8443 都不能用。
端口选择会连带影响证书和 CDN
这一步经常被忽略:端口选定之后,有两件事会跟着被限制。
证书签发方式被限死。HTTP-01 挑战需要验证方从 80 端口访问你的服务器,而 80 在这条线上永远不通——所以这条线上的证书只能走 DNS-01,通过 DNS 服务商的 API 加一条 TXT 记录来完成验证。这不是偏好问题,是唯一可行的路径。
CDN 代理不一定能用。如果想把这个地址挂到 CDN 后面,要先确认它代理哪些端口。常见的 CDN 只代理有限的一组 HTTPS 端口——443 和另外几个特定端口——你选的那个高位端口很可能不在其中。不在的话,DNS 记录必须保持直连模式(不开代理),否则请求根本到不了。
而一旦保持直连,CDN 提供的隐藏源站 IP、边缘缓存、DDoS 缓解就都没有了。这是个真实的取舍,不是配置细节。
回环 NAT:为什么内网测自己也不算数
上面提到内网连自己的公网 IP 不可靠,这里展开一下,因为它是第二常见的自欺方式(第一常见是代理)。
设想端口转发已经配好:路由器把公网 IP 的某个端口转到内网某台机器。现在从同一个内网的另一台机器访问「公网 IP:端口」,会发生什么?
数据包发到路由器,路由器发现目标是自己的公网地址,需要做目的地址转换(改成内网机器),转发出去。内网机器收到包,看到源地址是同一网段的内网地址,于是直接回给对方,不经过路由器。发起方等的是「来自公网 IP」的回包,收到的却是「来自内网 IP」的包,四元组对不上,直接丢弃。
结果就是连接超时——即使端口转发完全正确。支持回环 NAT 的路由器会在转发时同时改写源地址,让回包必须绕回路由器,从而闭合;不支持的就是上面这个结果。多数家用路由器属于后者,或者只在特定配置下支持。
反过来也成立:如果内网测「通」了,也不能证明公网访客能通——可能只是路由器做了回环,而运营商在更上游把这个端口封了。
两个方向都不可信,所以这条路径整个作废。唯一有效的是从真正的公网机器测。
DDNS 之前先确认地址会不会变
确认不是 CGNAT 之后,下一步通常是配 DDNS:定时把当前公网 IP 写进一条 A 记录。这里有个容易忽略的前置问题——先弄清这个地址多久变一次。
家宽的公网 IP 通常在拨号重连时变化,具体频率取决于运营商策略和你的重连频率。如果地址几周不变,DDNS 的更新间隔可以放宽;如果每天重拨,就得配合较短的 DNS TTL,否则解析缓存会指向一个已经失效的地址。
但 TTL 调小是有代价的:查询量上升,且对访客而言每次都要重新解析。这是个取舍,而做这个取舍需要先有数据——记录一段时间的地址变化,再决定 TTL 和更新间隔。
顺带一提,DDNS 脚本查当前 IP 时同样要用多源回退。用单一回显服务的话,那个服务挂掉的时候脚本可能写入一个错误的地址,或者干脆停止更新而不报错。
IPv6 不能想当然
这条线上网卡有真实的电信 IPv6 地址,看起来应该比 v4 简单——不需要 NAT,不需要端口转发。实测是全丢:从云端 ping 不通,连端口直接超时。
查下来是 v6 默认路由指向了内网另一台设备,入站要放行得在那台上改,而不是在目标机器上。这类拓扑在家庭网络里很常见——某台设备承担了路由或旁路网关的角色,v6 流量全部经过它。
更隐蔽的一个坑是怎么取自己的 IPv6 地址写进 AAAA 记录。直觉是用回显服务查一下,但那查到的是出站路径上的地址。这条线上出站 v6 经由那台旁路设备绕到了另一个服务商,回显服务报的地址跟本机网卡上的地址完全不同。
用回显服务的结果写 AAAA,指向的是一个跟你无关的地方。必须从本机网卡直接取地址:
ip -6 addr show scope global | grep inet6
出站和入站是两条独立的路径,这是整件事的核心——回显服务只能告诉你出站怎么走。
然后我们选择了完全绕开入站
做完这一整套测量,最后的结论是:家里的服务全部走隧道,不开任何入站端口。
隧道由本机主动向外建立长连接,服务通过这条连接被访问。于是上面那些限制——80/443 被封、8443 被占、CDN 端口不匹配、IPv6 入站不通、证书只能 DNS-01——一条都不再适用。不需要端口转发,不需要 DDNS,也不需要在路由器上开任何口子。
那这套测量白做了吗?没有。它的价值在两处:一是确认了这条线不是 CGNAT,意味着将来真需要直连入站时这条路是走得通的;二是排查「为什么直连不通」时,这些数字是现成的答案,不用重测一遍。
更一般的说法是:知道一条路走不通,和不知道它通不通,是两种不同的状态。前者可以让你放心地选另一条路,后者会让你在出问题时反复回来怀疑它。
隧道不是免费的,只是代价换了地方
说「隧道让所有限制都不适用」容易听成隧道是纯赚的。它有自己的代价,只是这些代价落在别处。
多一跳。访客的请求不再直达你家,而是先到隧道服务商的边缘节点,再沿着那条长连接下来。延迟因此增加,增加多少取决于访客、边缘节点、你家三者的地理关系。对网页和 API 通常无感,对延迟敏感的应用要实测。
多一个依赖。隧道服务商挂了,你的服务就没了——即使家里的机器和宽带一切正常。直连入站的故障域只有你自己和运营商,隧道多了一方。
连接是有状态的。隧道进程要一直活着。它崩了、网络抖动重连失败、或者升级后配置不兼容,服务就断。所以它必须由 init 系统托管并配置自动重启,不能手动 nohup 一下了事。这一点在长期运行中比想象的重要——手动起的进程会在某次重启后悄悄消失。
协议信息会变形。源站看到的连接来自本地回环,访客的真实 IP 和真实协议只存在于转发头里。任何依赖 REMOTE_ADDR 或本地协议变量的逻辑——访问控制、限流、生成绝对 URL、判断是否 HTTPS——都要改成读转发头,否则行为会很微妙地错。
这些代价都可控,而且比起「80 和 443 永远不通」显然划算。但它们是真实存在的,值得在选型时写下来,而不是等出问题时才发现。
方法论小结
不管测什么,这套流程都成立:
从被测对象之外的地方测量。本机测本机永远不可信,尤其是有代理接管网络栈的时候。
每次测量都带一个已知答案的对照。对照给出错误答案,这次读数就作废。这一条比任何具体的探测技巧都重要,因为它能拦住「工具坏了却以为是被测对象坏了」这一整类错误。
区分「不通」的原因,而不只是记录「不通」。被过滤、没监听、路由不可达,表现都是超时,但修法完全不同。对照端口就是用来做这个区分的。
不要把出站路径的观测当成入站事实。回显服务、出口 IP 查询、traceroute 出去的结果,都只描述出站。入站必须从外面打回来才能确认。