我们在浏览器里输入一个网址,比如:

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 握手的目标有三个:

  1. 验证服务器身份,确认它确实是目标域名的合法服务器。
  2. 协商加密算法、TLS 版本等参数。
  3. 安全地产生后续通信使用的对称加密密钥。

以现代 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 的 80443 端口。

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:原始协议,通常是 httphttps

后端应用经常依赖这些头生成回调地址、记录日志、判断是否 HTTPS、做 IP 限流等。

第八步:后端应用处理请求#

后端收到 Nginx 转发来的请求后,进入应用框架的处理流程。比如 Java、Go、PHP、Node.js、Python 等服务,内部大致会经历:

路由匹配
中间件处理:鉴权、日志、限流、CORS
Controller / Handler
业务逻辑
访问缓存、数据库、第三方服务
组装响应

比如 /api/users?page=1 可能会:

  1. 校验用户登录态。
  2. 解析分页参数。
  3. 查询 Redis 缓存。
  4. 缓存未命中时查询 MySQL。
  5. 把查询结果序列化成 JSON。
  6. 返回状态码、响应头和响应体。

后端返回给 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 仍然非常常见。

请求慢时,应该看哪里#

理解完整链路后,排查慢请求也更清晰。一次请求慢,可能慢在不同阶段:

阶段可能原因
DNSDNS 服务器慢、解析链路异常、缓存失效
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 跳转异常,本质上都能在这条路径里找到对应位置。