curl 怎么分别查看 DNS、连接和首字节耗时
来源:17golang原创
时间:2026-09-06 00:47:31 218浏览 收藏
只看 curl 的总耗时,只能知道“这次请求花了多久”,不能知道慢在 DNS、TCP 连接、TLS 握手、服务端计算还是响应传输。最实用的办法是用 --write-out(简写 -w)一次打印累计时间,再用相邻字段相减得到每个阶段的耗时。
time_namelookup、time_connect、time_appconnect、time_starttransfer和time_total都是从请求开始计算的累计时间。- DNS、TCP、TLS 等阶段时间不能直接把累计字段当结果,要用相邻字段做差。
--write-out配合-o /dev/null可以把正文丢弃,只保留适合日志记录的一行指标。- 代理、重定向、连接复用和单次网络抖动都会改变口径,定位服务端问题前要先排除这些因素。

curl 的时间字段分别代表什么
先记住一个关键点:curl 的时间变量默认是“从本次传输开始到某个节点完成”的累计秒数,而不是每一段独立的秒数。官方手册把 time_namelookup 定义为名称解析完成时间,把 time_connect 定义为 TCP 连接完成时间;HTTPS 场景还可以用 time_appconnect 表示 TLS 握手完成时间。
| 字段 | 它回答什么 | 常见用法 |
|---|---|---|
time_namelookup | 从开始到名称解析完成多久 | 观察 DNS 阶段的累计位置 |
time_connect | 从开始到 TCP 连接完成多久 | 与前一字段相减得到 TCP 耗时 |
time_appconnect | 从开始到 TLS/SSH 握手完成多久 | HTTPS 中与连接时间相减 |
time_pretransfer | 开始传输前的准备完成多久 | 覆盖握手及协议准备 |
time_starttransfer | 从开始到收到首字节多久 | 观察服务端等待和首包延迟 |
time_total | 完整操作结束多久 | 与首字节时间相减得到后续传输 |
所以标题中的“DNS、连接、首字节”分别对应 time_namelookup 的累计位置、time_connect 的累计位置,以及 time_starttransfer 的累计位置。它们不是三支互不相关的计时器,而是同一次请求上逐步增加的观察点。
怎样一次输出可比较的耗时数据
排查时建议把响应体丢到 /dev/null,同时保留错误信息和格式化指标。下面这条命令适合手工执行,也适合放进临时采样脚本:
curl -sS -o /dev/null \
-w 'code=%{http_code} remote=%{remote_ip}\nDNS=%{time_namelookup}s TCP=%{time_connect}s TLS=%{time_appconnect}s pre=%{time_pretransfer}s first_byte=%{time_starttransfer}s total=%{time_total}s\n' \
'https://example.com/health'
# -sS 保留错误信息;-o 丢弃响应体;-w 只输出本次传输的测量字段
-w 的格式字符串中,变量写成 %{变量名},换行写成 \n。输出中的 code 和 remote 不是耗时字段,但很重要:同一个域名可能解析到不同地址,状态码也能帮助你区分“服务端很快返回错误”和“服务端迟迟没有首字节”。
HTTP 明文请求没有 TLS 握手,time_appconnect 可能是零;不要因此把它当成一次失败。HTTPS 请求则要把证书和加密握手包含在连接分析里。为了让多次结果容易比较,尽量固定 URL、代理设置和请求方法,连续采样几次再看分布。
用字段差值判断慢在哪里
拿到一行输出后,按照下面的关系把累计值还原成阶段值:
DNS耗时 = time_namelookup
TCP耗时 = time_connect - time_namelookup
TLS耗时 = time_appconnect - time_connect
准备耗时 = time_pretransfer - time_appconnect
服务端等待 = time_starttransfer - time_pretransfer
响应传输 = time_total - time_starttransfer
# 每一行都使用相邻累计节点,避免把累计时间重复相加
例如 time_namelookup 很小但 time_connect - time_namelookup 很大,优先检查网络路径、代理或目标端口,而不是先改 DNS。若连接阶段正常,time_starttransfer - time_pretransfer 却明显偏大,才更像服务端排队、数据库查询或应用计算拖慢首包。首字节已经很快、但 time_total - time_starttransfer 很大,则应转向响应体大小、带宽和接收窗口排查。

结果异常时先排除四个测量边界
代理会改变连接的解释
如果环境设置了 HTTPS_PROXY 或显式使用 --proxy,time_connect 记录的可能是到代理的 TCP 连接,而不是到最终业务服务器的直连时间。文章里的差值仍然有效,但结论应写成“客户端到代理慢”或“代理之后慢”,不要直接归因于源站。
重定向会让一次请求包含多个 URL
加上 -L 后,curl 会跟随重定向。此时总耗时可能包含多个解析、连接和响应阶段,最后一组 remote_ip 也可能不是初始地址。要定位首个跳转,先不加 -L 看响应头,再分别测最终 URL。
连接复用会让后续请求看起来更快
同一次 curl 命令里请求多个 URL 时,curl 会尝试复用连接;但不同的 curl 进程之间不能复用连接。因此“同一命令第二次很快”与“单独启动一次 curl 很快”不是一个实验。压测或巡检要明确是否要测冷连接,并记录 num_connects 作为辅助信息。
单次读数不能代替连续采样
DNS 缓存、TCP 拥塞、代理负载和服务端排队都可能造成偶发尖峰。可以把格式字符串放入循环,每次记录时间戳、状态码、远端地址和五个时间字段,然后比较中位数与高分位,而不是只拿某一次最大值下结论。
常见问题
只想看首字节耗时,最少要输出哪个字段?
输出 %{time_starttransfer} 即可,但它仍是从请求开始算的累计时间。要看服务端从“准备完毕”到首字节的等待,应计算 time_starttransfer - time_pretransfer。
为什么 DNS 耗时和 time_namelookup 不是总能对上?
在单 URL、没有特殊代理和重定向的简单请求中,二者可以近似对应;遇到 DoH、代理、连接复用或多次传输时,实际解析路径和统计边界会变化,应结合完整输出判断。
curl 输出的 total 比各段直接相加小,正常吗?
正常。累计字段之间存在重叠,正确做法是使用相邻字段相减;不要把所有累计字段相加,否则会重复计算。
把 time_namelookup、time_connect、time_appconnect、time_pretransfer、time_starttransfer 和 time_total 一起记录,再用相邻值做差,curl 就不只是“请求是否成功”的工具,也能成为一把边界清楚的网络排查尺。
-
426 收藏
-
387 收藏
-
242 收藏
-
238 收藏
-
402 收藏
-
423 收藏
-
221 收藏
-
400 收藏
-
422 收藏
-
198 收藏
-
270 收藏
-
488 收藏
-
125 收藏
-
143 收藏
-
440 收藏
-
161 收藏
-
171 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习