网络连通性排查顺序:DNS、TCP、TLS、HTTP 每层一条命令,四条报错对四个层
"连不上"是四件不同的事:名字解析不出来、端口不通、TLS 握手失败、HTTP 返回错误码。按 DNS → TCP → TLS → HTTP 的顺序每层打一条命令,任何一层失败就停在那层,不要往下猜。这个顺序的好处是每层只依赖上一层的结果,不会出现"DNS 错了却在查证书"这种浪费时间的情况。
四层各一条命令
| 层 | 一条命令 | 通过判据 | 失败时的典型报错 |
|---|---|---|---|
| DNS | getent hosts example.com 或 dig +short A example.com | 返回至少一个 A/AAAA 记录 | Could not resolve host / status: NXDOMAIN |
| TCP | nc -z -w3 example.com 443 或 timeout 5 bash -c "cat < /dev/null > /dev/tcp/example.com/443" | 无输出且退出码 0 | Connection refused / Connection timed out |
| TLS | openssl s_client -connect example.com:443 -servername example.com -brief | CONNECTION ESTABLISHED + Verification: OK | wrong version number / self-signed certificate |
| HTTP | curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/ | 期望的状态码 | 404/500 等(连通性没问题,是应用层) |
一条命令看完整链路耗时,比逐层手打更快:
curl -sS -o /dev/null \
-w 'code=%{http_code} dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s total=%{time_total}s\n' \
--max-time 15 https://example.com/
实测输出:
code=200 dns=0.001446s tcp=0.002181s tls=1.118325s total=1.756965s
四个数字正好对应四层:dns 是名字解析,tcp 是 TCP 握手完成(相对开始),tls 是 TLS 握手完成,total 是整次请求。这一次 TLS 用了 1.118 秒,占 1.757 秒总耗时的 64%,而 DNS 和 TCP 都在 2 毫秒级别——"慢"发生在 TLS 层,不在网络上。
四层失败各打一次
$ curl -sS --max-time 8 http://no-such-host-kernelranch.invalid/
curl: (6) Could not resolve host: no-such-host-kernelranch.invalid
$ curl -sS --max-time 8 http://127.0.0.1:9/
curl: (7) Failed to connect to 127.0.0.1 port 9 after 0 ms: Couldn't connect to server
$ curl -sS --max-time 8 https://example.com:80/
curl: (35) OpenSSL/3.0.22: error:0A00010B:SSL routines::wrong version number
三个退出码就是三条定位结论:6 是 DNS,7 是 TCP,35 是 TLS。curl 的退出码比任何日志都直接,脚本里判断 $? 就能分流。HTTP 层不产生 curl 错误码(除非 --fail),它把状态码原样返回,404 和 500 都算"连通成功"。
四个坑
第一,用 ping 判断"通不通"。很多主机禁 ICMP,ping 不通但 443 正常;反过来 ping 通也不代表端口开着。判断端口用 nc -z 或 /dev/tcp,不要用 ping。
第二,DNS 有多条 A 记录时的"时通时不通"。本次 getent hosts example.com 返回两个地址(172.66.147.243、104.20.23.154),客户端可能只连其中一个。这种情况用 curl --resolve 固定到某台,能立刻判断是不是某一台有问题:
curl -sv --resolve example.com:443:172.66.147.243 https://example.com/
第三,IPv6 优先导致"客户端超时、服务端无日志"。getent hosts 会同时给出 AAAA,客户端按系统策略优先连 IPv6;如果服务端只监听 IPv4,就会在 TCP 层超时,而服务端连 SYN 都收不到。用 curl -4 / -6 分别试一次,能在一分钟内确认。
第四,把 TLS 握手慢当成"网络慢"。上面那 1.118 秒的 TLS 耗时里,包含了 TCP 之上的密钥交换、证书链传输与验证,可能来自证书链过长、OCSP 抓取超时、或者服务端 CPU 紧张。区分方法很简单:time_connect 小、time_appconnect 大,就是 TLS 段的问题;两者都大才是网络 RTT 的问题。
把四层检查写成一个脚本
#!/bin/bash
H=$1; P=${2:-443}
getent hosts "$H" >/dev/null || { echo "DNS fail"; exit 6; }
timeout 5 bash -c "cat < /dev/null > /dev/tcp/$H/$P" 2>/dev/null || { echo "TCP fail"; exit 7; }
openssl s_client -connect "$H:$P" -servername "$H" -brief </dev/null 2>&1 \
| grep -q "CONNECTION ESTABLISHED" || { echo "TLS fail"; exit 35; }
curl -sS -o /dev/null -w "HTTP %{http_code}\n" --max-time 15 "https://$H/" || exit 1
三段退出码与 curl 的 6/7/35 对齐,好处是同一套编号在脚本和人工排查里通用,看到 35 就知道不用再查 DNS。脚本里 getent hosts 只判"有没有记录",不判记录是否正确——域名指向了错误地址时它照样返回成功,所以 DNS 层的最终确认还是要看一眼解析出来的 IP 是否符合预期。
容器与代理:两层额外变量
容器内的网络排查多两个变量。一是网络命名空间:容器有自己的 resolv.conf、自己的路由表、自己的 iptables,宿主上 curl 通不代表容器里通,反之亦然,所以命令要在出问题的那一侧执行。二是代理环境变量:HTTP_PROXY、HTTPS_PROXY、NO_PROXY 在 CI 镜像里经常被全局注入,一旦命中,curl 的流量会先到代理,前面四层的结论全部失效。curl -v 的 Connected to <proxy>:<port> 是识别它的最快方式。
这两层都不在 DNS→TCP→TLS→HTTP 的链条里,却能让链条上的结论全错,所以脚本跑完之后仍要人工看一眼 curl -v 的 Connected to 那一行。
为什么是这个顺序
四层的依赖关系是单向的:没有 DNS 结果就没有目标地址,没有 TCP 连接就没法开始 TLS 握手,没有 TLS 会话就发不出 HTTPS 请求。按依赖方向排查,每一步的失败都能被上一步的成功所排除;反过来从 HTTP 报错往回猜,会同时面对"名字错了""端口封了""证书过期"三种可能,最后还是要回到最前面重来。
另一个好处是耗时分布自然暴露瓶颈。四层的耗时量级差别很大:DNS 通常是毫秒级(有缓存时接近 0),TCP 是 RTT 量级,TLS 多一到两个 RTT,HTTP 取决于服务端处理时间。本次实测里 DNS 1.4ms、TCP 2.2ms、TLS 1118ms,一眼就能看出异常集中在 TLS 段,不需要再逐层加日志。
小结
四层排查的关键是"每层只回答一个问题",并且用机器可读的退出码或耗时字段固化结论。日常排障用 curl -w 一条命令拿到四层耗时,脚本里用退出码 6/7/35 分流,人工确认某台机器用 --resolve 绕开 DNS。
拿到分层结论之后,查的方向也就定了:DNS 层查解析记录与 resolv.conf,TCP 层查安全组、监听地址、防火墙,TLS 层查证书与协议版本,HTTP 层查应用日志。四层之外还有一层常被忽略——代理。容器或 CI 里如果设了 HTTP_PROXY,前面四层全部正常但流量走了别处,curl -v 的 Connected to 一行会显示代理地址,一眼能看出来。
数据来源与检索时间
| 来源 | URL | 对应数据 |
|---|---|---|
| man7.org · ss(8) | https://man7.org/linux/man-pages/man8/ss.8.html | -t/-n/-l 的用法、TCP 状态名(syn-sent/time-wait 等)用于判断卡在握手哪一步 |
| docs.openssl.org · openssl-s_client | https://docs.openssl.org/3.5/man1/openssl-s_client/ | -connect/-servername/-brief 的语义、wrong version number 出现的原因(对非 TLS 端口发起握手) |
| man7.org · ss(8) | https://man7.org/linux/man-pages/man8/ss.8.html | state syn-sent 过滤可用于确认 SYN 是否发出、是否有回包 |
检索时间:2026-11-10(Asia/Shanghai)。
核验说明
- 真跑:
getent hosts/dig +short A对example.com的解析结果、nc -z与/dev/tcp的连通性判断、openssl s_client -brief的协议版本、curl -w的四段耗时(dns=0.001446s tcp=0.002181s tls=1.118325s total=1.756965s),均在debian:12-slim容器内实测。 - 真跑:四类失败报错原文(
curl: (6)、curl: (7)、curl: (35) wrong version number、HTTP 404)为本机实际输出。 - 未验证:
--resolve固定到单台地址后对"时通时不通"的复现未构造;IPv6 优先导致超时的场景未构造(容器内无独立 IPv6 出口);代理层那一节属经验说明,未在本次实验中设置HTTP_PROXY验证。 - 仅依据文档:
ss的state syn-sent过滤用法按ss(8)记录,本次未用它做过实际抓包对照。