一次 HTTP 请求的完整旅程:从浏览器到 Nginx 再到后端服务

我们在浏览器里输入一个网址,比如:
https://www.example.com/api/users
看起来只是按下回车,页面就返回了。但在这几百毫秒甚至几十毫秒里,浏览器、操作系统、DNS、TCP、TLS、Nginx、后端应用、数据库和缓存可能都参与了一次协作。
这篇文章按时间顺序,把一次典型的 HTTPS 请求完整拆开:从浏览器发起请求,到 TCP 三次握手、TLS 密钥协商,再到 Nginx 接收连接、反向代理给后端服务,最后响应返回浏览器。
总览:请求链路长什么样#
可以先看一个简化版流程图:
用户输入 URL
│
▼
浏览器解析 URL
│
▼
DNS 解析域名 -> 得到服务器 IP
│
▼
TCP 三次握手 -> 建立可靠连接
│
▼
TLS 握手/密钥协商 -> 建立加密通道
│
▼
浏览器发送 HTTP 请求
│
▼
Nginx 接收请求
│
├─ 匹配 server_name
├─ 匹配 location
├─ 处理静态资源 / gzip / header / 日志
│
▼
proxy_pass 反向代理到后端应用
│
▼
后端应用处理业务逻辑
│
▼
后端返回 HTTP 响应
│
▼
Nginx 返回响应给浏览器
│
▼
浏览器解析 HTML/CSS/JS 并渲染页面
实际生产环境里,中间还可能有 CDN、负载均衡、WAF、Service Mesh、网关、缓存、队列、数据库等组件。但核心路径大体就是上面这些层次。
第一步:浏览器解析 URL#
浏览器拿到 URL 后,会先拆出几个关键信息:
https://www.example.com/api/users
│ │ │
协议 域名 路径
其中:
https表示使用 HTTP over TLS,默认端口是443。www.example.com是目标主机名。/api/users是请求路径。- 如果 URL 里带 query string,比如
?page=1,也会一起进入 HTTP 请求。
浏览器还会检查缓存、HSTS 规则、代理设置、Cookie、Service Worker 等。如果缓存命中,甚至可能不真正访问服务器。
下面假设浏览器需要访问远端服务器。
第二步:DNS 解析域名#
浏览器不能直接拿域名建立 TCP 连接,它需要先知道域名对应的 IP 地址。
DNS 查询通常按这个顺序尝试:
浏览器 DNS 缓存
↓
操作系统 DNS 缓存
↓
hosts 文件
↓
本地 DNS 服务器,例如路由器、运营商 DNS、公共 DNS
↓
根域名服务器
↓
顶级域名服务器,例如 .com
↓
权威 DNS 服务器
↓
返回 A/AAAA/CNAME 等记录
常见记录包括:
A:域名到 IPv4 地址。AAAA:域名到 IPv6 地址。CNAME:当前域名是另一个域名的别名。
得到 IP 后,浏览器才知道应该向哪台机器发起连接。
第三步:TCP 三次握手#
HTTP/1.1 和 HTTP/2 在常见 HTTPS 场景下都跑在 TCP 上。所以浏览器拿到 IP 后,需要先建立 TCP 连接。
TCP 三次握手是这样的:
客户端 服务器
│ │
│ SYN, seq = x │
├──────────────────────────────────────────>│
│ │
│ SYN + ACK, seq = y, ack = x + 1 │
│<──────────────────────────────────────────┤
│ │
│ ACK, ack = y + 1 │
├──────────────────────────────────────────>│
│ │
连接建立 连接建立
三次握手解决的核心问题是:双方都确认自己和对方的收发能力是正常的。
- 第一次:客户端告诉服务器,我想建立连接。
- 第二次:服务器告诉客户端,我收到了,并且我也准备好了。
- 第三次:客户端告诉服务器,我也收到了你的确认。
这一步完成后,只是建立了可靠的 TCP 通道。此时数据还没有加密。如果是 http://,浏览器可以直接发送 HTTP 请求;如果是 https://,还要继续 TLS 握手。
第四步:TLS 握手与密钥协商#
很多人会说 SSL 握手,但现在实际使用的主流协议是 TLS。SSL 是更早的协议,已经不推荐使用。日常表达里说“SSL 证书”问题不大,但技术上 HTTPS 主要依赖 TLS。
TLS 握手的目标有三个:
- 验证服务器身份,确认它确实是目标域名的合法服务器。
- 协商加密算法、TLS 版本等参数。
- 安全地产生后续通信使用的对称加密密钥。
以现代 TLS 1.2/1.3 的常见思路简化来看:
客户端 服务器
│ │
│ ClientHello │
│ 支持的 TLS 版本、加密套件、随机数 │
├──────────────────────────────────────────>│
│ │
│ ServerHello │
│ 选择 TLS 版本、加密套件、服务器随机数 │
│ Certificate │
│ 服务器证书链 │
│ Key Share / ECDHE 参数 │
│<──────────────────────────────────────────┤
│ │
│ 验证证书 │
│ 生成共享密钥材料 │
│ │
│ Finished │
├──────────────────────────────────────────>│
│ │
│ Finished │
│<──────────────────────────────────────────┤
│ │
加密通道建立 加密通道建立
证书验证做了什么#
浏览器收到服务器证书后,会检查:
- 证书域名是否匹配,比如证书是否包含
www.example.com。 - 证书是否过期。
- 证书是否由受信任的 CA 签发。
- 证书链是否完整。
- 证书是否被吊销,实际实现可能涉及 OCSP 或 CRL。
如果证书验证失败,浏览器就会显示“不安全”或证书错误页面。
为什么要先做非对称,再用对称加密#
非对称加密适合身份验证和密钥协商,但性能较低。对称加密速度快,适合大量传输 HTTP 数据。
所以 TLS 的典型策略是:
握手阶段:用非对称密码学安全协商密钥
传输阶段:用协商出的对称密钥加密 HTTP 数据
现代 TLS 通常使用 ECDHE 这类临时密钥交换方式。它的一个重要好处是前向安全:即使未来服务器私钥泄露,过去抓到的流量也不能直接被解密。
第五步:浏览器发送 HTTP 请求#
TLS 通道建立后,浏览器才会把 HTTP 请求放进加密通道里发送。
一个 HTTP 请求大概长这样:
GET /api/users?page=1 HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 ...
Accept: application/json
Cookie: session_id=...
如果是表单提交或接口调用,也可能是:
POST /api/users HTTP/1.1
Host: www.example.com
Content-Type: application/json
Content-Length: 38
{"name":"Tom","email":"tom@example.com"}
HTTP 请求由几部分组成:
- 请求行:方法、路径、协议版本。
- 请求头:Host、Cookie、User-Agent、Accept、Authorization 等。
- 请求体:POST、PUT、PATCH 等请求常见。
在 HTTPS 中,这些 HTTP 内容都会被 TLS 加密。中间网络设备能看到目标 IP、端口、连接时序和部分 TLS 元信息,但看不到明文的 URL path、Cookie、请求体等内容。
第六步:请求到达 Nginx#
如果服务器使用 Nginx,对外暴露的通常是 Nginx 的 80 或 443 端口。
Nginx 收到连接后,会做几类事情:
监听端口 listen 443 ssl
↓
根据 SNI / Host 匹配 server 块
↓
完成 TLS 终止,得到明文 HTTP 请求
↓
根据 URI 匹配 location
↓
决定处理方式:静态文件、重定向、反向代理、限流、鉴权等
一个典型配置可能是:
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/nginx/certs/example.com.crt;
ssl_certificate_key /etc/nginx/certs/example.com.key;
location /static/ {
root /var/www/example;
expires 7d;
}
location /api/ {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
upstream app_backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
这里有几个重点。
TLS 终止#
所谓 TLS 终止,就是 HTTPS 加密连接到 Nginx 为止。Nginx 使用证书私钥完成握手,然后解密出 HTTP 请求。
后续 Nginx 到后端应用之间,可以是:
- HTTP 明文,常见于可信内网。
- HTTPS 加密,常见于跨网络、零信任或合规要求较高的环境。
- Unix Socket,常见于 Nginx 和应用在同一台机器上的场景。
server 匹配#
一台 Nginx 可以托管多个域名。请求进来后,Nginx 会通过 SNI 和 Host 选择对应的 server 配置。
SNI 是 TLS 握手里的 Server Name Indication。因为 TLS 握手发生在 HTTP 请求之前,所以服务器需要通过 SNI 知道客户端想访问哪个域名,从而选择正确证书。
location 匹配#
进入某个 server 后,Nginx 会根据请求路径匹配 location:
location /api/ { ... }
location /static/ { ... }
location = /health { ... }
匹配结果决定请求是读静态文件、转发后端、返回固定内容,还是做重定向。
第七步:Nginx 反向代理到后端#
如果请求命中 proxy_pass,Nginx 会作为客户端,再向后端应用发起请求。
这里的“反向代理”可以理解为:
浏览器不知道真实后端是谁
浏览器只访问 Nginx
Nginx 再替浏览器去访问后端应用
反向代理常见作用包括:
- 隐藏后端真实地址。
- 统一入口,便于证书、日志、限流、鉴权。
- 负载均衡,把请求分发到多台后端。
- 连接复用,减少后端连接开销。
- 静态资源和动态接口分流。
- 灰度发布、蓝绿发布、故障摘除。
Nginx 转发时通常会补充一些请求头:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
这些头的作用是让后端知道原始请求信息。
Host:原始访问域名。X-Real-IP:直接连接 Nginx 的客户端 IP。X-Forwarded-For:代理链路上的客户端 IP 列表。X-Forwarded-Proto:原始协议,通常是http或https。
后端应用经常依赖这些头生成回调地址、记录日志、判断是否 HTTPS、做 IP 限流等。
第八步:后端应用处理请求#
后端收到 Nginx 转发来的请求后,进入应用框架的处理流程。比如 Java、Go、PHP、Node.js、Python 等服务,内部大致会经历:
路由匹配
↓
中间件处理:鉴权、日志、限流、CORS
↓
Controller / Handler
↓
业务逻辑
↓
访问缓存、数据库、第三方服务
↓
组装响应
比如 /api/users?page=1 可能会:
- 校验用户登录态。
- 解析分页参数。
- 查询 Redis 缓存。
- 缓存未命中时查询 MySQL。
- 把查询结果序列化成 JSON。
- 返回状态码、响应头和响应体。
后端返回给 Nginx 的响应可能是:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
{"users":[{"id":1,"name":"Tom"}]}
如果出错,也可能是:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{"message":"internal server error"}
第九步:Nginx 返回响应给浏览器#
Nginx 收到后端响应后,不一定原样返回。它可能继续做一些处理:
- 压缩响应,比如 gzip 或 brotli。
- 添加或修改响应头。
- 写 access log 和 error log。
- 缓存响应。
- 处理 upstream 超时、重试、错误页。
- 对大文件或流式响应做缓冲控制。
然后 Nginx 把响应写回之前那条 TLS 连接。由于浏览器和 Nginx 之间仍然是 HTTPS,响应内容会被 TLS 加密后通过 TCP 发回浏览器。
第十步:浏览器接收并渲染#
浏览器收到响应后,会根据 Content-Type 决定如何处理。
如果是 HTML:
解析 HTML
↓
发现 CSS、JS、图片、字体等资源
↓
继续发起更多 HTTP 请求
↓
构建 DOM、CSSOM
↓
执行 JS
↓
布局、绘制、合成
↓
显示页面
所以打开一个页面通常不是一次请求,而是一批请求。HTML 只是入口,后续还会加载 CSS、JS、图片、接口数据等。
Keep-Alive、HTTP/2 和连接复用#
如果每个资源都重新 DNS、TCP、TLS 一遍,开销会很高。所以现代浏览器会尽量复用连接。
HTTP/1.1 Keep-Alive#
HTTP/1.1 默认支持长连接。多个请求可以复用同一条 TCP/TLS 连接,但同一连接上请求处理仍然容易受到队头阻塞影响。
HTTP/2 多路复用#
HTTP/2 允许多个请求在同一条 TCP 连接上并发传输,使用 stream 区分不同请求。这样减少了连接数量,也降低了重复握手成本。
不过 HTTP/2 仍然基于 TCP,所以如果底层 TCP 丢包,所有 stream 都可能受到影响。
HTTP/3 和 QUIC#
HTTP/3 基于 QUIC,运行在 UDP 上。它把连接建立、加密和多路复用做了更深的整合,可以减少握手延迟,并缓解 TCP 层面的队头阻塞。
但在很多传统后端架构中,Nginx 反向代理 HTTP/1.1 或 HTTP/2 仍然非常常见。
请求慢时,应该看哪里#
理解完整链路后,排查慢请求也更清晰。一次请求慢,可能慢在不同阶段:
| 阶段 | 可能原因 |
|---|---|
| DNS | DNS 服务器慢、解析链路异常、缓存失效 |
| TCP 握手 | 网络延迟高、丢包、防火墙策略异常 |
| TLS 握手 | 证书链过长、算法配置不合理、未开启会话复用 |
| Nginx 接入 | worker 不足、连接数耗尽、限流、配置匹配异常 |
| 反向代理 | upstream 连接慢、后端不可用、重试导致耗时变长 |
| 后端应用 | 业务逻辑慢、锁竞争、线程池耗尽、GC、慢 SQL |
| 数据库/缓存 | 慢查询、索引缺失、缓存击穿、连接池不足 |
| 响应传输 | 响应体过大、带宽不足、客户端网络差 |
常见排查工具包括:
curl -v https://www.example.com/
curl -w "dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n" -o /dev/null -s https://www.example.com/
curl -w 可以把 DNS、TCP、TLS、首字节时间和总耗时拆开看,非常适合定位慢在哪一层。
总结#
一次 HTTPS 请求可以简单记成这条主线:
URL -> DNS -> TCP -> TLS -> HTTP -> Nginx -> upstream -> response
其中:
- DNS 负责把域名变成 IP。
- TCP 负责建立可靠传输通道。
- TLS 负责身份验证、密钥协商和加密通信。
- HTTP 负责表达请求和响应语义。
- Nginx 负责接入、TLS 终止、路由、静态资源、反向代理和日志。
- 后端应用负责真正的业务处理。
把这条链路串起来之后,很多线上问题都会变得更容易定位:证书错误、连接超时、502、504、慢接口、跨域、真实 IP 获取错误、HTTPS 跳转异常,本质上都能在这条路径里找到对应位置。