我不会起名字322· 后端 / 算法 / 数据库
📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL
文章目录
- curl 能通但程序连不上:4 类环境差异与排查顺序
- 一、这个现象说明什么
- 二、差异 1:IPv4 / IPv6
- 验证
- 修法
- 三、差异 2:代理环境变量
- 验证
- 四、差异 3:超时设置与 DNS 解析方式
- 五、差异 4:TLS 证书与 SNI
- 另一个高频:SNI
- 验证
- 六、还有一类:容器里的网络差异
- 七、排查顺序
- 八、带走物
curl 能通但程序连不上:4 类环境差异与排查顺序
一、这个现象说明什么
curl能通,说明:
DNS ✅ 能解析出 IP TCP ✅ 能建立连接 TLS ✅ 握手能过 HTTP ✅ 能拿到响应网络链路是通的。那程序连不上,问题一定出在你和 curl 不一样的地方。
所以排查的核心思路是:逐个对比 curl 和你的程序在哪些地方不同。
二、差异 1:IPv4 / IPv6
这是最常见的一类,而且现象特别迷惑:curl 毫秒级返回,程序卡到超时。
原因:
curl 会尝试 IPv4 和 IPv6(Happy Eyeballs),哪个快用哪个 Go 的 net.Dial("tcp", ...) 也会解析 A 和 AAAA 但如果你的网络 IPv6 不通(包被丢弃而不是拒绝),AAAA 的查询或连接会一直等到超时而 curl 因为有 Happy Eyeballs(通常 250ms 就切 IPv4),所以看起来"很快"。
验证
# 看域名解析出哪些地址getent ahosts api.example.com# 10.0.1.20 STREAM# 2001:db8::1 STREAM ← 如果有 IPv6 而网络不通,就是它# 分别强制测试curl-4-m3-o/dev/null-s-w"%{http_code}\n"https://api.example.comcurl-6-m3-o/dev/null-s-w"%{http_code}\n"https://api.example.com如果-6超时而-4正常,就是它。
修法
// 明确只用 IPv4conn,err:=net.Dial("tcp4","api.example.com:443")// HTTP transporttr:=&http.Transport{DialContext:func(ctx context.Context,network,addrstring)(net.Conn,error){return(&net.Dialer{Timeout:5*time.Second}).DialContext(ctx,"tcp4",addr)},}或者在系统层面禁用 IPv6 / 配/etc/gai.conf让 IPv4 优先。
三、差异 2:代理环境变量
curl 会读http_proxy/https_proxy环境变量,很多程序不会(或者反过来)。
env|grep-iproxy# https_proxy=http://proxy.corp:8080# no_proxy=localhost,127.0.0.1,.internal如果你的环境必须走代理才能访问外网,而:
- curl 读了环境变量 → 通;
- Go 的
http.DefaultTransport也会读(ProxyFromEnvironment)…… 但如果你自定义了 Transport 却忘了设 Proxy,就不会走代理 → 连不上。
tr:=&http.Transport{// 忘了这一行,自定义 transport 就不走代理Proxy:http.ProxyFromEnvironment,// ...}反过来的情况也常见:程序走了代理,但代理里没有目标域名(no_proxy没配),导致本该直连的走了代理然后失败。
验证
# 强制不走代理试一下curl--noproxy'*'-m3https://api.example.com# 看程序侧到底连的哪个 IPss-tnp|grep<your-pid>四、差异 3:超时设置与 DNS 解析方式
curl 的默认行为和你程序可能完全不同:
| curl | 典型程序 | |
|---|---|---|
| 连接超时 | 有默认(约 300s,但通常你会加-m) | 可能没设(Go 默认无超时!) |
| DNS 缓存 | 无(每次查) | Go 有自己的 resolver |
| 重试 | 无 | 可能有 |
Go 里最经典的坑:http.Client{}没有超时。
// ❌ 危险:没有超时,可能永久挂住client:=&http.Client{}resp,err:=client.Get(url)// ✅ 正确:必须有超时client:=&http.Client{Timeout:10*time.Second}没有超时时,现象是"程序卡住不动",而 curl 因为你加了-m 3所以正常。
另外Go 默认用自己的 Go resolver 而不是系统的(PreferGo),行为差异包括:
- 读取的
/etc/resolv.conf相同,但不读/etc/hosts之外的一些配置; - 对
search域、ndots的处理略有差异; - 不读 nscd 缓存。
// 想用系统 resolver(走 nscd)varresolver=&net.Resolver{PreferGo:false}五、差异 4:TLS 证书与 SNI
curl 和程序对证书的校验可能不同:
- curl 用系统 CA 仓库(
/etc/ssl/certs); - Go 也用系统 CA,但容器里可能没装
ca-certificates; - 如果目标用了自签证书,curl 加了
-k就跳过校验,程序没跳过就失败。
x509: certificate signed by unknown authority另一个高频:SNI
curl 会自动带 SNI(Server Name Indication),但如果你在程序里直接连 IP 然后用 Host 头,可能没带 SNI:
// ❌ 连 IP 但没设 SNI,HTTPS 会失败或被路由到默认虚拟主机conn,_:=net.Dial("tcp","10.0.1.20:443")tlsConn:=tls.Client(conn,&tls.Config{InsecureSkipVerify:false})// ✅ 要么连域名,要么显式设 ServerNametlsConn:=tls.Client(conn,&tls.Config{ServerName:"api.example.com",})验证
# 看证书链openssl s_client-connectapi.example.com:443-servernameapi.example.com# 忽略证书测试(确认是不是证书问题)curl-k-m3https://api.example.com六、还有一类:容器里的网络差异
如果程序跑在容器里、curl 跑在宿主机上(或者反过来),差异会更多:
- 容器的
/etc/resolv.conf和宿主机不同(K8s 里指向 CoreDNS); - 容器的网络命名空间不同,iptables 规则不同;
- 容器内的
/etc/hosts没有宿主机的自定义条目。
排查铁律:在和程序完全相同的环境里测。
# 进到同一个容器里测kubectlexec-it<pod>--sh# 然后 curlcurl-m3https://api.example.com# 或者起一个同样镜像的临时容器kubectl run-it--rmdebug--image=<same-image>--restart=Never --sh在宿主机上 curl 成功,不能证明容器里也能通。这一步做错,后面全白查。
七、排查顺序
1. 在程序所在的同一个环境(同一个容器/同一台机器)里 curl └─ 不通 → 那是网络/hosts/代理问题,先解决这个 2. 分别测 IPv4 和 IPv6 curl -4 / curl -6 └─ -6 超时 → IPv6 问题,程序里用 tcp4 3. 对比代理环境变量 env | grep -i proxy curl --noproxy '*' 对比 └─ 有差异 → 程序 Transport 里加 Proxy: http.ProxyFromEnvironment 4. 检查程序的超时设置 └─ 没有 Timeout → 加上(Go 的 http.Client 默认无超时) 5. 检查 TLS curl -k 对比 → 如果 -k 能通就是证书问题 连 IP 时要显式设 ServerName 6. 在程序里打印实际连的地址 └─ 确认连的是不是你以为的那个 IPGo 侧一个便于排查的技巧:打印实际建立连接的地址:
tr:=&http.Transport{DialContext:func(ctx context.Context,network,addrstring)(net.Conn,error){log.Printf("dial %s %s",network,addr)return(&net.Dialer{Timeout:5*time.Second}).DialContext(ctx,network,addr)},}八、带走物
| curl 通、程序不通的常见原因 | 判据 | 修法 |
|---|---|---|
| IPv6 不通 | curl -6超时 | 用tcp4/ 禁用 IPv6 |
| 代理 | `env | grep proxy` 有值 |
| 程序没设超时 | 程序卡住不返回 | http.Client{Timeout: ...} |
| 证书 | curl -k能通 | 装 CA / 或配tls.Config |
| SNI | 连 IP + HTTPS | 设ServerName |
| 容器网络不同 | 宿主机通、容器不通 | 在容器里 curl,不要跨环境判断 |
一句话结论:curl 能通说明链路没问题,问题一定在"你和 curl 不一样的地方"——按 IPv6、代理、超时、TLS/SNI 四类逐个对比,并且一定要在程序所在的那个环境里测。