手机当服务器:Termux IPv6 公网 SSH 与 DDNS 自动解析

发布时间:2026/9/30 18:26:17
手机当服务器:Termux IPv6 公网 SSH 与 DDNS 自动解析 手机当服务器用最难受的从来不是性能是地址。我用 Termux 在一台安卓机上跑常驻服务快两年了真正把大多数人劝退的环节只有一个手机重启一次、切一次 Wi-Fi、基站换一个SSH 就直接连不上。这篇内容要解决的就是这条链路——怎么在 Termux 里把 IPv6 公网地址挖出来、怎么确认它真的从外部可达、怎么把 sshd 挂上去最后用一套自动解析把变来变去的地址固定到一个域名上让你在任意网络环境下都能连回自己的手机。需要先说清楚一个前提这套方案成立的唯一条件是你的手机本身能拿到全球单播 IPv6 地址。如果所在网络只有 IPv4或者运营商没有下发 IPv6 前缀那这里讲的所有东西都跑不起来这是物理层面的硬边界不是配置技巧能绕过去的。适合读这篇的人有三类手里有闲置安卓机想当低功耗小主机的人、想随时 SSH 回手机跑脚本的人、以及已经知道 IPv6 但被地址老变、连不上、连上卡折磨过的人。下面按确认地址、验证可达、挂服务、做解析、划安全线、排故障这条真实顺序往下走中间所有命令都是我实测能跑的。1. 公网 IPv6 落到手机上究竟改变了什么1.1 从地址不够用到每台设备一个全球地址IPv4 时代家用宽带大多只有一个公网地址剩下的设备全躲在路由器后面外部想主动连进来必须靠端口映射而端口映射的前提是你在路由器上手动配置并保证路由器本身有公网入口。到了移动网络这里情况更糟——大量终端共享同一批地址外部主动连接基本无从谈起。IPv6 的设计目标之一就是恢复端到端可达地址空间足够大运营商可以直接给每台在线设备下发一个全球单播地址中间不需要地址转换。对 Termux 用户来说这意味着一个很直接的结果手机上的服务可以直接监听在自己的全球地址上外部设备只要也有 IPv6 就能直连中间没有 NAT 这一层。这个变化的价值不在能省几块钱服务器钱而在于链路的确定性和延迟。直连没有中转节点路径长度由路由决定实测同一台手机用直连和走中转往返延迟差别经常是几十毫秒级别的。当然代价也很明确没有了 NAT 这层天然遮挡你的设备就是全网可扫描的一台主机这件事后面第 5 节会专门讲。1.2 蜂窝数据与 Wi-Fi两条完全不同的取址路径很多人第一次在 Termux 里看到一长串 IPv6 地址时是懵的原因在于不同接入方式拿到的地址来源完全不同。搞不清这一点后面所有的排查都会变成瞎猜。蜂窝数据路径运营商侧通过标准流程把前缀下发到手机手机在这个前缀里生成自己的接口标识。这种情况下手机通常能直接拿到一个全球单播地址前缀落在2400::/12这个大段里具体是哪个子段由运营商决定不同地区不同运营商都不一样。这是最容易成功的一条路因为中间没有你自己的设备参与配置。Wi-Fi 路径家庭宽带光猫从运营商拿到前缀后需要路由器继续往下分发手机才能拿到全球地址。这里有两个常见的断点一是路由器只给自己分配了地址而没有把前缀继续分下去二是路由器在 IPv6 防火墙里默认丢弃所有入站连接。第一条表现为手机根本没有全球地址第二条表现为有地址但外部死活连不上——这两种症状看起来一样但处理方式完全不同。我在不同运营商和不同路由器上反复试过蜂窝数据的成功率明显高于 Wi-Fi。所以如果你只是想尽快跑通验证一遍建议先用蜂窝数据把整条链路走通再去折腾家里的路由器。1.3 动手之前必须先确认的三件事在敲第一行命令之前请先在心里过一遍这三个问题任何一个答案是否定的后面就都是白费功夫手机当前接入的网络是否真的下发了 IPv6 前缀这个不看你手机的设置界面而是要看实际地址第 2 节会给具体方法。运营商或路由器是否对入站连接做了过滤这个从手机侧看不出来只能从外部设备实测。你要暴露的服务是什么如果只是 SSH风险相对可控如果打算开放 Web 服务或数据库端口务必先看第 5 节。把这三件事排好序的好处是你遇到连不上时不会到处乱改配置。我见过太多人一上来就改 sshd 配置、换端口、关认证方式最后发现问题根本不在服务端而在于客户端压根没有 IPv6 出口。2. 在 Termux 里把地址和可达性摸清楚2.1 三条命令交叉验证本机 IPv6 状态Termux 是运行在安卓应用沙箱里的很多标准 Linux 网络命令在这里会受到系统权限限制这一点必须先接受否则你会怀疑自己的操作。下面三条命令按可靠性从高到低排列建议三条都跑一遍交叉验证。第一条直接读内核暴露的接口表这是最稳的基本不受 SELinux 影响cat /proc/net/if_inet6输出每行的结构是32 位十六进制地址、接口编号、前缀长度、作用域、标志位、接口名。看到fe80开头的那些直接忽略它们是链路本地地址跨网段没有任何意义。第二条用 iproute2pkg install iproute2 -y ip -6 addr show scope global部分安卓版本尤其是定制 ROM会因为权限问题让这条命令输出为空或者报 netlink 相关的错误。如果遇到这种情况不要慌回到第一条命令就行。第三条net-tools 里的老命令pkg install net-tools -y ifconfig这条命令在新版安卓上时好时坏能出结果就看一眼出不来就别纠结。我的实际经验是把/proc/net/if_inet6当作唯一真相来源其他两条只作为辅助确认。2.2 地址前缀怎么读全球单播、临时地址与链路本地拿到一串地址之后关键动作是判断它属于哪一类。下面这张表是我自己整理出来贴在手边的对照表规律很简单看开头几位。地址开头类型能否被外部直连fe80::链路本地地址不能仅同链路有效fc00::/fd00::唯一本地地址不能等同内网地址2400::至240f::全球单播常见于移动网络可以前提是入站未被过滤2001::至2003::全球单播其他规划段可以视运营商而定::ffff:开头IPv4 映射地址不适用本质是 IPv4除了类型还要注意一个容易踩的点安卓在默认配置下会启用隐私扩展也就是除了那个稳定的接口标识之外还会额外生成若干个临时地址生存周期较短。你在/proc/net/if_inet6里看到的可能有一堆地址最终哪个能被用来连入并不确定。我的做法是先拿所有全球单播地址逐个做外部测试确认哪一个稳定可达然后优先使用基于稳定接口标识的那个通常对应stable-privacy或固定的eui64标识。如果实在分不清就在路由器或 DNS 侧记录所有候选地址——AAAA 记录本身支持多条客户端会自动挑能通的。2.3 出口连通性自测从 ping 到 curl -6确认了本机有全球地址下一步要确认的是这条路径真的能把包发出去并且回来。这里有两层本机的出站能力以及外部对你入站的可见性。先测出站pkg install iputils curl -y ping -6 -c 3 2400:3200::1 curl -6 -s --max-time 8 https://api6.ipify.org第一条用 IPv6 目标做 ICMP 探测第二条强制走 IPv6 访问一个仅回显 IPv6 地址的服务。第二条返回的那个地址就是你在公网上被看到的样子。正常情况下它应该和你本机if_inet6里的某个全球地址完全一致如果不一致说明中间存在地址转换这种情况下从外部直连基本不可能成功需要回头确认网络环境。再测入站这一步必须换一台设备用另一个网络比如手机开热点给电脑、或者找一台别的网络的机器ping -6 -c 3 2400:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx nc -6 -vz 2400:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx 8022注意 IPv6 地址在使用时必须用方括号包起来这是在 ssh、curl、scp 里通用的写法。如果 ICMP 不通但 TCP 通了说明对方只是屏蔽了 ICMP不影响使用如果两个都不通那大概率是入站被过滤了直接跳到第 6 节按顺序排查。3. 把 Termux 变成可被外部登录的 SSH 服务端3.1 装 openssh 与账号初始化这一步本身不难但有几个细节如果搞错后面会出现服务起来了但登不上。先装基础包pkg update pkg upgrade -y pkg install openssh -y whoamiwhoami输出的不是root而是形如u0_a123这样的应用用户名这是正常的——Termux 运行在安卓的应用沙箱里本来就没有系统 root。这个用户名就是你后面登录时要用的账号名务必记下来。接着设置登录密码passwd这里有个坑我踩过密码输入时屏幕完全没有任何回显连星号都没有这不是卡死了正常输入回车即可。如果密码设得太短某些版本会提示但依然接受为了安全建议直接用密钥登录密码只作为应急手段。如果要定期从固定设备登录把客户端的公钥写进来mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys # 粘贴客户端公钥后按 CtrlD chmod 600 ~/.ssh/authorized_keys权限位必须严格authorized_keys是 600、.ssh目录是 700。sshd 对这两个权限非常敏感文件权限过松会直接拒绝使用密钥而且报错信息里不会明说原因只会在日志里留下一行 Authentication refused: bad ownership or modes这就是典型的服务没毛病但就是登不上。3.2 sshd_config 里真正要改的几行Termux 里 sshd 的配置文件在$PREFIX/etc/ssh/sshd_config也就是$PREFIX/etc/ssh/sshd_config改之前先备份一份原文件这一点很重要因为改错了会导致 sshd 起不来而你又回不到出厂状态。需要动的字段不多我把自己实际用的最小改动列出来配置项建议值作用Port8022默认值非 root 环境下能绑定的端口AddressFamilyany同时接受 IPv4 和 IPv6 连接PasswordAuthenticationno关闭密码登录只留密钥PubkeyAuthenticationyes启用密钥登录PermitRootLoginno即使有 root 也禁止直接登录MaxAuthTries3减少暴力尝试空间LoginGraceTime20缩短认证窗口ClientAliveInterval60保活探测间隔ClientAliveCountMax3连续 3 次无响应断开AddressFamily这一项不要随手设成inet6。设成inet6之后 sshd 只会监听 IPv6万一你的 IPv6 路径临时不可用就连本地 IPv4 的救急登录都做不到了。留成any是更务实的取舍。改完启动sshd ss -tlnp | grep 8022第二条命令用来确认端口真的在监听。如果ss不可见用ss -tln也能看只是没有进程名。看到监听记录之后再去做第 3.4 节的完整验证。3.3 非 root 环境下的端口现实为什么是 8022这是很多人第一个卡住的地方为什么不能直接用 22 端口原因很直接Linux 内核规定 1024 以下的端口属于特权端口只有具备相应权限的进程才能绑定而 Termux 里的 sshd 是以应用用户身份运行的绑不了 22。所以 8022 不是推荐配置而是当前权限模型下的结果。有人会想通过改端口来隐藏服务认为用非标准端口更安全。这里必须说清楚在公网环境下靠换端口带来的安全性提升非常有限。全端口扫描是常规操作从常见的 22、2222、8022 一直到高位端口扫一遍的时间成本很低。端口号唯一的实际作用是减少日志噪音让你在翻 auth 日志时少看几千行无效尝试。真正有效的防护是密钥认证加严格权限而不是端口号。这一点想明白了你就不会把精力浪费在换个没人知道的端口这种思路上。3.4 第一次从外部连入的完整验证链路从客户端发起连接ssh -p 8022 -i ~/.ssh/id_ed25519 u0_a123[2400:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]如果嫌每次输地址太长可以在客户端的~/.ssh/config里固化下来Host phone HostName 2400:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Port 8022 User u0_a123 IdentityFile ~/.ssh/id_ed25519 ServerAliveInterval 30 ServerAliveCountMax 3之后直接ssh phone就行。ServerAliveInterval这几行在移动网络下很重要后面 6.2 节会解释原因。验证的顺序建议是先在手机本地ssh -p 8022 u0_a123localhost确认服务本身没问题再从同一 Wi-Fi 下的另一台设备用地址连最后才从完全不同的网络连。这个顺序能把服务端问题和网络路径问题彻底分开省下大量瞎试的时间。4. 地址会变所以解析必须自动化4.1 手机上的 IPv6 为什么活不长这是整个方案里最需要接受的一点手机上拿到的全球地址稳定性远不如一台固定服务器。变化来源主要有三个一是接入网络切换。从 Wi-Fi 切到蜂窝前缀完全换了一套地址必然改变。二是前缀重新下发。即使一直在同一个网络运营商侧的前缀也可能在重启、重新拨号、租期到期后变化。三是隐私扩展带来的临时地址轮换接口上的可用地址集合本身就在变。所以记住一个地址然后手填这种做法本质上只在几分钟到几小时的时间尺度内有效。想让它可用就必须把当前地址这件事自动化成一条持续更新的记录而这条记录最自然的载体就是域名解析——也就是往 AAAA 记录里写当前的 IPv6 地址。4.2 拿到出口地址几个可用的探测方式写 DDNS 脚本的第一步是拿到别人连我时该用的那个地址。有两种获取方式各有前提方式一从本机接口读ip -6 addr show scope global | grep inet6 | awk {print $2} | cut -d/ -f1优点是快、无外部依赖缺点是在接口不可用的 ROM 上会失败而且如果本机有多个全球地址你得知道该选哪个。方式二从外部服务回显curl -6 -s --max-time 8 https://api6.ipify.org优点是拿到的就是你在公网上的实际样子缺点是需要外部网络可用。我的做法是两者都取然后做一次比对一致就用它不一致说明中间可能做了地址转换此时直连方案本身就不可靠应该停下来重新评估网络环境而不是硬着头皮往下写脚本。这个比对逻辑只有几行代码但帮我提前发现过好几次环境问题。4.3 自建 DDNS用脚本把 AAAA 记录改成当前地址下面这个脚本是我实际在用的结构把里面的接口地址和令牌换成你自己的就行。这里以通用 DNS 服务商的 API 为例其他服务商的字段名不同但思路完全一样。#!/data/data/com.termux/files/usr/bin/bash set -uo pipefail CF_TOKENyour_api_token ZONE_IDyour_zone_id RECORD_IDyour_record_id RECORD_NAMEtermux.example.com STATE_FILE$HOME/.termux_ipv6_last # 取当前出口 IPv6 NEW_IP$(curl -6 -s --max-time 10 https://api6.ipify.org) if ! echo $NEW_IP | grep -qE ^[0-9a-fA-F:]$; then echo $(date %F %T) 获取地址失败跳过 $HOME/.termux_ipv6.log exit 1 fi # 和上次比较没变就不动 DNS OLD_IP$(cat $STATE_FILE 2/dev/null || echo ) if [ $NEW_IP $OLD_IP ]; then exit 0 fi # 更新 AAAA 记录 RESP$(curl -s --max-time 15 -X PUT \ https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID} \ -H Authorization: Bearer ${CF_TOKEN} \ -H Content-Type: application/json \ --data {\type\:\AAAA\,\name\:\${RECORD_NAME}\,\content\:\${NEW_IP}\,\ttl\:60,\proxied\:false}) if echo $RESP | grep -q success:true; then echo $NEW_IP $STATE_FILE echo $(date %F %T) 更新成功 $OLD_IP - $NEW_IP $HOME/.termux_ipv6.log else echo $(date %F %T) 更新失败 $RESP $HOME/.termux_ipv6.log fi有几个设计上的取舍值得说明。第一先比对再更新。很多免费 DNS 接口对写入频率有限制如果每分钟都盲写一次很容易被限流加上状态文件比对可以把请求量降到极低。第二proxied必须设为 false。开了 CDN 代理之后解析出来的地址会变成代理节点地址而我们要的是直连开了反而连不上。第三TTL 设成 60 秒。手机地址变化频繁TTL 太长会让客户端长时间拿旧地址60 秒是可用性和查询量之间的平衡点。第四记录名建议用独立子域。比如termux.example.com不要占主域名避免以后想给主站做其他配置时互相干扰。4.4 定时、重试与日志让它自己跑起来脚本写完不会自己跑需要一个调度。Termux 里有两个方向轻量方案是用 crontabpkg install cronie -y crond crontab -e内容是每分钟跑一次* * * * * /data/data/com.termux/files/home/ddns.sh配合脚本里的先比对再更新一分钟一次并不会造成压力因为绝大多数执行会在比对那一步就退出。想要更省电的话可以改成每 5 分钟一次代价是地址变化后有最多 5 分钟的连不上窗口。这个取舍取决于你的使用习惯我自己的配置是 2 分钟一次兼顾了响应速度和唤醒频率。另一个必须处理的问题是开机自启。安卓会自动清理后台进程cron 也不例外手机重启之后一切归零。需要装 Termux:Boot 这个配套应用然后在~/.termux/boot/目录下放一个启动脚本mkdir -p ~/.termux/boot cat ~/.termux/boot/start-services.sh EOF #!/data/data/com.termux/files/usr/bin/sh termux-wake-lock sshd crond EOF chmod x ~/.termux/boot/start-services.shtermux-wake-lock这一行千万别省它的作用是让系统不要因为省电策略把 Termux 的进程挂起。这是我踩过最深的坑之一服务配置全对脚本也正常但手机息屏十分钟后 SSH 就再也连不上日志显示 sshd 还在跑实际进程已经被系统冻结了。加上这行之后问题消失。5. 公网可达的另一面先把安全边界划出来5.1 全端口扫描是常态不是意外有全球地址意味着你的设备直接暴露在公网上没有任何中间层帮你过滤。这不代表立刻会被攻击但意味着你必须假设有人正在扫描。实际观察下来的情况是一个全新的 IPv6 地址挂上 sshd 之后通常在几小时到一天之内就会在认证日志里看到来自不同来源的尝试记录。理解这一点之后安全策略的思路就很清楚了不追求没人发现我而是追求发现了也进不来。这个思路下密钥认证、最小权限、日志可查就是三个必须做到的基础项而端口号、隐藏服务这类做法优先级很低。还有一个容易被忽略的暴露面sshd 不是唯一在监听的进程。Termux 里跑的其他脚本、临时启的 Web 服务、甚至某个库自带的调试端口都会一起暴露。定期用ss -tln检查一遍监听列表是我现在的固定习惯每次跑完总能看到一两个自己都忘了的进程。5.2 密钥登录 关闭密码最小必要改动这套改动只需要三步但效果最明显客户端生成密钥推荐 ed25519ssh-keygen -t ed25519 -a 100 -C termux-phone把公钥写进手机的~/.ssh/authorized_keys权限按 3.1 节设置。在sshd_config里设置PasswordAuthentication no并重启 sshd。之所以要关密码登录不是因为密码一定弱而是因为密码登录允许对方无限次尝试密钥登录几乎不给机会。开了密钥之后即使有人找到你的地址和端口也基本没有可行的进入路径。这里有一个务实的提醒关掉密码登录之后如果密钥丢了就彻底进不去了。所以要么在另一台设备上留一份私钥备份要么在手机本地保留一个可以通过物理接触登录的方式。我自己是把手上的私钥加密备份了一份放在完全离线的介质里这是最后一道保险。5.3 监听地址、端口与时间窗口的收窄技巧如果希望进一步收紧有几个不太费力但有效的做法。限制来源网段。sshd_config支持按来源地址做匹配把认证方式进一步绑定到特定范围Match Address 2001:db8:1234::/48,203.0.113.0/24 PasswordAuthentication no PubkeyAuthentication yes这样即使将来临时打开了别的入口也只有指定来源的可信设备能走完整的认证流程。注意Match块要写在文件末尾且块内不能出现某些全局指令写之前先确认语法改错了 sshd 会直接拒绝启动。收窄监听地址。如果确定只用某一个全球地址连入可以在配置里显式指定减少接口上其他地址的暴露。不过由于手机地址会变这条对动态环境不太友好一般我用AddressFamily any就够了。控制服务运行时间。如果你的使用场景是每天固定几小时需要连那完全可以用 cron 定时开关 sshd其余时间不开监听。这是最彻底的收窄方式代价是失去随时接入的便利。我在早期用过这个方案配合脚本在需要的时间段前后启动和关闭服务实测下来很安心。5.4 手机侧的存活问题省电策略与 wakelock安卓的省电机制对长驻进程非常不友好这里必须做三步处理缺一步都会出现凌晨能连白天连不上这类诡异现象。第一步关闭系统对 Termux 的电池优化。在系统设置的电池管理里把 Termux 标记为不受限制不同系统路径不一样一般叫电池优化或后台限制。第二步保持 wakelock。前面提到的termux-wake-lock就是做这件事的它会在系统层面申请一个持有锁。执行后可以在通知栏看到对应提示。需要注意的是这个锁在设备重启后失效所以必须放到开机启动脚本里。第三步接受系统可能随时回收这个事实把服务做成可恢复的。我的做法是写一个巡检脚本每几分钟检查一次 sshd 和 crond 是否在运行不在就拉起来。这类脚本只要几行但对稳定性提升非常明显#!/data/data/com.termux/files/usr/bin/bash if ! pgrep -x sshd /dev/null; then sshd echo $(date %F %T) sshd 已重启 $HOME/.termux_supervisor.log fi巡检日志我保留了一个月回头看能很清晰地看出系统在什么时间点回收过进程对判断设备是否需要换一台很有帮助。6. 连不上和连上但很卡两类故障的排查顺序6.1 分层排查从地址到端口到应用遇到连不上最忌讳的操作是随便挑一个环节去改。正确的做法是按客户端出口、网络路径、服务端监听、认证这四层依次确认任何一层不通过就停在那里解决不要往下走。第一层客户端有没有 IPv6 出口。在客户端跑curl -6 -s --max-time 8 https://api6.ipify.org如果这里就报错或者超时说明你所在的网络完全没有 IPv6 能力后面的问题根本不用查。这一层的失败率远超其他层尤其是很多人用的办公网络、公共 Wi-Fi。第二层地址是否解析正确。用dig AAAA termux.example.com查一下和手机上当前的实际地址比。不一致说明 DDNS 脚本没跑成功去看脚本日志。这里有个细节本地可能有 DNS 缓存明明记录已经更新了但查出来还是旧的换个公共解析服务再查一次就能确认。第三层端口是否可达。用nc -6 -vz 地址 8022从外部测配合手机上ss -tln确认监听状态。如果监听正常但外部测不通问题在网络路径或入站过滤不在服务端。第四层认证是否通过。如果前面三层都通那问题就落在密钥或权限上直接看客户端的ssh -vvv输出错误信息会明确告诉你卡在哪一步。按这个顺序走绝大多数问题五分钟内就能定位。我早期最大的时间浪费就是不走这个顺序看哪条配置不顺眼就改一下结果把原本正常的部分也改坏了。6.2 MTU 与路径问题导致的能连上但卡死这是一类非常典型且容易被误诊的故障连接能建立密钥认证也能过但登录进去之后命令执行极慢或者执行到一半直接卡住不动。很多人第一反应是手机性能不行其实往往是路径上的 MTU 不匹配导致的。IPv6 规范里不允许中间设备做分片只能由发送方根据路径 MTU 来分片一旦链路上某个节点丢弃了需要分片的包却不回报 ICMPv6 消息就会出现这种连得上但传不动数据的现象。移动网络下的 MTU 值经常和标准值不同跨网络访问时更容易触发。排查方法是逐步探测可用包长ping -6 -c 3 -M do -s 1400 目标地址 ping -6 -c 3 -M do -s 1200 目标地址从较大的值往下试找到能稳定通的最大值然后用这个结果去推断可用 MTU。如果发现 1400 不通而 1200 通说明链路上有节点把 MTU 压得比较低。客户端侧的缓解办法是尽量让传输走已有的连接而不是频繁重连ServerAliveInterval和ClientAliveInterval就是干这个的。另外交互式使用场景下如果对卡顿特别敏感可以考虑用更容忍高延迟的交互方式纯 SSH 在高丢包环境下体验确实一般。需要说明的是在非 root 环境下没办法直接调整接口的 MTU 参数所以这一层主要是靠探测定位问题然后通过改变路径或使用方式来规避。6.3 只有 IPv4 出口的场景这是硬边界必须坦白讲清楚一个问题如果你常用的网络环境只能提供 IPv4那这套基于 IPv6 直连的方案就是用不了。这不是配置问题而是协议栈层面的现实。我见过太多人在这上面耗掉一整个周末反复怀疑是端口、防火墙、密钥出了问题实际上客户端根本没有 IPv6。判断方法很简单客户端执行前面那条curl -6命令失败就是没有。这里有个容易混淆的点域名解析出 AAAA 记录不代表你能连上解析成功只是拿到了地址能不能通取决于客户端的网络能力。域名同时配了 A 记录和 AAAA 记录时客户端会根据自己的能力自动选择所以从某些网络能连、从另一些不能是完全正常的现象不是服务端故障。如果你确实需要在 IPv4 环境下也能接入那需要换一套完全不同的思路和本文讲的直连方案不是一回事。我的建议还是先确认自己的主要使用场景里有没有 IPv6如果有就按本文的路径做如果没有趁早换方案不要在这里死磕。6.4 错误信息对照表与快速处置下面这张表是我从实际日志里一条条积累出来的对应关系遇到报错直接查表比盲目搜索快得多。客户端报错最可能的原因处置方向Network is unreachable客户端没有 IPv6 出口换网络或确认接入方式No route to host地址格式不对或缺省路由缺失检查方括号写法、地址是否完整Connection timed out路径不通或入站被过滤用 nc 测端口确认路由器防火墙Connection refused能到主机但端口无监听手机上检查 sshd 是否运行Permission denied (publickey)认证失败查 authorized_keys 权限和内容Host key verification failed服务端密钥变了清理本地 known_hosts 对应条目连上后命令无响应MTU 或路径质量差按 6.2 节做包长探测半夜断连、白天正常系统省电回收了进程加 wakelock 和巡检脚本这张表里我特别想强调最后两行。它们的特点是看起来不像网络问题所以最容易被误判成硬件或系统故障。而实际上一个是传输层参数一个是系统电源策略都属于只要知道了原因就能很快解决的类型。我自己现在的做法是把这张表直接写成一个文本文件放在手机里每次出问题先对一遍对不上的再从头排查。这个习惯让我把平均排障时间从半小时压到了几分钟——比起研究各种高级配置把已知问题的处置路径固化下来实际收益要大得多。最后再补一个小经验把域名和验证命令一起存在手机的备忘录里包括curl -6的探测命令和dig AAAA的查询命令。因为最容易出问题的时刻往往是你人在外面、手上只有手机、没有任何参考资料的时候能一键复制粘贴几条命令直接确认状态比什么都管用。