网站打开慢,问题可能出在域名解析、网络连接、HTTPS 握手,也可能是网站程序迟迟没有返回内容。
如果没有先确定慢在哪个阶段,就直接增加带宽、升级服务器或修改缓存设置,往往很难判断改动是否有效。
本文介绍一种实用的排查方法:使用 curl 输出一次请求的各项耗时,先找到值得继续检查的阶段,再结合服务器日志和浏览器工具定位原因。
1. 先明确:curl 测的是一次请求
浏览器打开一个网页,通常需要请求 HTML、CSS、JavaScript、图片和接口数据,还要执行脚本并完成页面渲染。
而本文的 curl 命令,只请求指定的一个 URL。
例如,测试网站首页时,curl 会获取首页返回的内容,但不会像浏览器一样继续下载 HTML 中引用的全部图片和脚本。
因此,测试之前先确定目标:
- 首页迟迟没有响应,先测首页 URL。
- 某个接口很慢,测对应的接口 URL。
- 图片下载慢,测具体图片地址。
- 页面内容已经出现,但操作仍然卡顿,需要进一步检查前端执行和渲染。
建议先选择自己网站中一个无需登录、没有写入操作的公开页面。
另外,测试位置也要一致。服务器访问自身的结果,不能直接代表用户从家庭宽带或移动网络访问时的体验。
2. 用 curl 输出一次请求的耗时
2.1 检查是否安装 curl
以下命令适用于 Linux、macOS 或 Windows WSL 中的终端。
先执行:
curl --version
如果 Debian 或 Ubuntu 提示找不到命令,可以安装:
sudo apt-get update
sudo apt-get install -y curl ca-certificates
使用 root 用户时,可以去掉命令前面的 sudo。
2.2 执行耗时测试
将下面的 https://www.example.com/ 替换为自己要检查的实际 HTTPS 地址:
curl -q --http1.1 \
--connect-timeout 10 \
--max-time 30 \
-sS -o /dev/null \
-w 'HTTP=%{http_code}\nIP=%{remote_ip}\nDNS=%{time_namelookup}s\nTCP_DONE=%{time_connect}s\nTLS_DONE=%{time_appconnect}s\nREADY=%{time_pretransfer}s\nFIRST_BYTE=%{time_starttransfer}s\nTOTAL=%{time_total}s\n' \
'https://www.example.com/'
这里使用 -w 在请求结束后输出指定字段,-o /dev/null 丢弃响应内容,因此终端主要显示状态码、连接 IP 和耗时。响应内容仍然会被下载,只是不保存到文件。
命令设置了 10 秒连接超时和 30 秒总超时,避免异常请求一直等待。
为便于比较,本文统一使用 HTTP/1.1。浏览器可能使用 HTTP/2 或 HTTP/3,因此两者结果可能不同。
另外,-q 用于跳过默认 curl 配置文件,但不会清除终端中的代理环境变量。如果终端配置了 HTTP 或 HTTPS 代理,需要把代理路径也考虑进去。
3. 看懂这六个时间:它们是累计值
这是使用 curl 分析耗时最容易弄错的地方:
这些字段大多表示“从请求开始,到某个阶段完成”的累计时间,不能直接相加。
| 输出名称 | 对应字段 | 表示什么 |
|---|---|---|
| DNS | time_namelookup | 从开始到域名解析完成 |
| TCP_DONE | time_connect | 从开始到 TCP 连接完成 |
| TLS_DONE | time_appconnect | 从开始到 TLS 握手完成 |
| READY | time_pretransfer | 从开始到准备开始传输 |
| FIRST_BYTE | time_starttransfer | 从开始到收到响应首字节 |
| TOTAL | time_total | 本次操作的总耗时 |
这些字段的单位都是秒。对于本文这种单次 HTTPS 请求,判断某一阶段的耗时,需要看相邻时间点的差值。
例如:
TCP_DONE=0.045s 不代表 TCP 建连单独花了 45 毫秒,因为这个累计时间中还包含前面的域名解析过程。
同样,FIRST_BYTE=1.291s 也不能直接理解成网站程序执行了 1.291 秒。
下面的差值分析,适用于请求成功、直连目标、没有跳转、没有连接复用的单次 HTTPS 请求。如果使用代理、跟随多次跳转,或者请求发生错误,就不能直接照搬。

4. 用一组示例,计算慢在哪个阶段
假设得到以下结果。
这是一组用于说明计算方法的示例数据,不是某个网站的实测结果。
HTTP=200
IP=203.0.113.10
DNS=0.015000s
TCP_DONE=0.045000s
TLS_DONE=0.090000s
READY=0.091000s
FIRST_BYTE=1.291000s
TOTAL=1.351000s
先把累计时间换算成毫秒,再计算差值:
| 阶段 | 计算方式 | 示例结果 |
|---|---|---|
| DNS 解析 | DNS | 15 毫秒 |
| TCP 建连相关区间 | TCP_DONE − DNS | 30 毫秒 |
| TLS 握手相关区间 | TLS_DONE − TCP_DONE | 45 毫秒 |
| 准备传输后到收到首字节 | FIRST_BYTE − READY | 1200 毫秒 |
| 首字节后到请求完成 | TOTAL − FIRST_BYTE | 60 毫秒 |
从这组数据看,最值得继续检查的是:
连接和握手完成后,到收到首字节之间的等待。
但这 1200 毫秒仍不能直接等同于“程序执行时间”。该区间还可能包含请求发送、网络往返、代理转发、服务端排队及处理等因素。curl 的首字节时间本身也包含前面的连接准备时间。
正确的下一步,是记录测试时间和 URL,然后对照反向代理、应用及数据库日志,继续查找等待发生在哪里。
5. 根据耗时选择下一步排查方向

有了时间分布后,可以按下面的方向继续检查:
| 观察到的现象 | 优先检查什么 |
|---|---|
| DNS 解析耗时反复偏高 | 当前解析器、解析缓存、DNS 请求是否异常 |
| TCP 建连区间偏长 | 网络路径、连接重试、目标端口及服务连接压力 |
| TLS 握手区间偏长 | 网络往返、TLS 握手情况及相关服务日志 |
| 准备传输后等待首字节很久 | 应用处理、数据库查询、反向代理、CDN 回源与缓存情况 |
| 首字节很快,但请求结束很晚 | 响应体大小、传输速率、服务端是否持续分段输出 |
| curl 很快,浏览器仍然慢 | 图片、脚本、接口请求以及前端执行和渲染 |
这张表用于确定排查方向,不能仅凭某一项数值就认定故障原因。
例如,等待首字节较久,可能与数据库慢查询有关,也可能是 CDN 没有命中缓存,需要等待源站返回。
首字节后的耗时较长,也不一定就是带宽不足。响应体较大、服务端分批生成内容,都会拉长这段时间。
如果网站已经接入 CDN,IP 显示的通常是本次实际连接的节点地址,不能直接当成源站 IP;使用代理时,还可能显示代理地址。
需要对照测试新服务器或指定源站时,可以参考本站文章:网站迁移前如何测试新服务器。使用域名配合指定解析测试,可以保留正常的主机名和 HTTPS 证书校验条件。
6. 不要只测一次,也不要忽略状态码
6.1 连续测试,观察是否稳定
一次请求可能恰好遇到网络波动、缓存未命中或临时负载。
可以连续测试 5 次,每次间隔 1 秒:
for i in 1 2 3 4 5; do
curl -q --http1.1 \
--connect-timeout 10 \
--max-time 30 \
-sS -o /dev/null \
-w "第${i}次 HTTP=%{http_code} IP=%{remote_ip} 首字节=%{time_starttransfer}s 总耗时=%{time_total}s\n" \
'https://www.example.com/'
sleep 1
done
同样需要替换为自己网站的实际地址。
重点观察:
- 是否每次都慢,还是偶尔出现一次明显偏高。
- 状态码是否一致。
- 实际连接的 IP 是否变化。
- 首次访问与后续访问是否有明显差异。
这只是少量请求的排查,不是压力测试。重复执行也不会自动清空系统 DNS 缓存或 CDN 缓存。
6.2 先确认返回的是正常页面
响应很快,并不代表访问正常。
例如:
- 返回
301或302,可能只测到了跳转响应。 - 返回
403,可能拿到的是访问拒绝页面。 - 返回
404,可能测试地址写错了。 - 返回
502,需要先处理网关或上游服务异常。 - 显示
000,表示没有取得 HTTP 状态码,应先查看 curl 输出的错误信息。
本文的主命令没有添加 -L,因此不会自动跟随跳转。
如果发现返回 301 或 302,可以查看响应头:
curl -q --http1.1 \
--connect-timeout 10 \
--max-time 30 \
-sS -D - -o /dev/null \
'https://www.example.com/'
找到 Location 指向的目标,确认最终访问地址后,再对最终 URL 执行前面的耗时测试。
这样能避免把多个请求的时间混在一起分析。跟随跳转时,curl 的相关累计耗时会受到前面请求的影响。
如果返回的是 502 Bad Gateway,可以继续参考本站:Nginx出现502 Bad Gateway怎么办?常见原因与排查教程。
7. curl 很快,为什么浏览器还是慢?
如果首页的 curl 请求很快,但浏览器依然需要很久才能显示完整页面,接下来应检查页面中的其他资源。
以 Chrome 为例:
- 打开开发者工具,进入 Network(网络)。
- 保持开发者工具打开,勾选 Disable cache(停用缓存)。
- 重新加载页面。
- 先查看页面文档请求,再检查图片、JavaScript 和接口请求。
- 点击耗时较长的请求,在 Timing(时间) 中查看具体阶段。
Chrome 的 Network 面板可以记录页面请求,并进一步显示单个请求的等待与下载等时间。
常见情况包括:
- HTML 返回很快,但页面引用的图片体积过大。
- 页面必须等待某个接口返回后,才能显示主要内容。
- 第三方脚本或外部资源响应较慢。
- 网络请求已经结束,浏览器仍在执行脚本或渲染页面。
如果主要时间花在脚本执行和渲染上,就需要继续查看 Performance(性能)面板。
对照 curl 和浏览器结果时,还要留意 Cookie、登录状态、缓存、HTTP 版本及请求头是否一致。两个请求条件不同,耗时就不能简单地直接比较。
8. 结尾:先找到慢的阶段,再决定怎么优化
排查网站打开慢,可以先从一个具体 URL 开始,使用 curl 查看 DNS、连接、TLS、首字节和总耗时,再根据时间差值缩小检查范围。
其中最需要记住的是:累计时间不能相加,首字节耗时不等于程序执行时间,一次 URL 请求也不等于整个网页的加载过程。
当你能明确说出“慢在解析”“慢在建立连接”或“连接后等待响应较久”,后续检查日志、调整缓存和优化程序就会更有方向,也更容易验证改动是否有效。








