---
URL: "/zh-CN/blog/2026-06/Nginx-Forward.html"
LLMS_URL: "/zh-CN/blog/2026-06/Nginx-Forward.md"
lastUpdated: true
commentabled: true
recommended: true
title: "为什么 Nginx 明明监听了 80，转发后端时却用了 4xxxx 端口？"
description: "为什么 Nginx 明明监听了 80，转发后端时却用了 4xxxx 端口？"
date:

pageClass: "blog-page-class"
cover: "/covers/nginx.svg"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: 为什么 Nginx 明明监听了 80，转发后端时却用了 4xxxx 端口？
description: 为什么 Nginx 明明监听了 80，转发后端时却用了 4xxxx 端口？
date: 2026-06-30 09:35:00
pageClass: blog-page-class
cover: /covers/nginx.svg
---

我第一次认真看 Nginx 反向代理链路的时候，也被两个现象绕住过。

_第一个现象是_：

Nginx 明明监听的是：

```text
0.0.0.0:80
```

但一到它转发后端时，抓到的连接却经常是这样：

```text
client:50001 -> nginx:80
nginx:40001 -> backend:8080
```

我当时最直接的疑问就是：

> 既然 Nginx 都已经有 80 端口了，为什么它连后端时不用 80，而要再冒出一个 40001？

_第二个现象是_：

不开 `upstream keepalive` 时，压测一上来，代理机上很快就能看到大量连接创建和关闭；开了 `keepalive` 以后，这一段又明显稳定很多。

于是问题又变成：

> 连接池到底复用了什么？它到底是在复用客户端连接，还是在复用 Nginx 到后端的连接？

这两个问题看起来像 Nginx 配置问题，本质上其实是同一个问题：_反向代理不是一条连接直通，而是两段 TCP 连接_。

## 一、Nginx 反向代理，不是一条连接穿过去

很多人脑子里会把反向代理想成这样：

```text
client -> nginx -> backend
```

看起来像一条连接从客户端一路穿到后端。

但 TCP 层面不是这么工作的。

真实情况是两段连接：

```text
client:50001 -> nginx:80
nginx:40001 -> backend:8080
```

_第一段里_：

- 客户端是客户端
- Nginx 是服务端

_第二段里_：

- Nginx 变成客户端
- backend 变成服务端

也就是说，Nginx 并不是把客户端那条 TCP 连接“递给后端”。

_它做的是_：

- 先接住客户端请求
- 解析 HTTP
- 再由自己建立到后端的连接
- 把请求转发过去
- 收到响应以后再回给客户端

这一步如果没想清楚，后面两个问题都会绕。

## 二、为什么前面监听了 80，后面连后端时却不能继续用 80？

因为监听 80 和连接后端，根本不是同一个 socket，也不是同一个角色。

_当你写_：

```nginx
server {
    listen 80;
}
```

这表示 Nginx 在本机监听 80，职责是：

```text
等别人连进来
```

所以你会看到很多客户端都在连：

```text
clientA:50001 -> nginx:80
clientB:50002 -> nginx:80
clientC:50003 -> nginx:80
```

这些连接可以同时存在，是因为每条 TCP 连接都由四元组唯一确定：

```text
源 IP + 源端口 + 目标 IP + 目标端口
```

虽然它们的目标都是 `nginx:80`，但客户端 IP 或客户端端口不同，所以四元组不同。

但 Nginx 一旦去连后端，它的身份就变了。

这时候它不再是“等人连进来”的服务端，而是“主动发起连接”的客户端。

假设后端是：

```text
backend:8080
```

那 Nginx 正常会建立这样的连接：

```text
nginx:40001 -> backend:8080
nginx:40002 -> backend:8080
nginx:40003 -> backend:8080
```

这里的 40001、40002、40003，就是系统给它分配的本地临时端口。

### 为什么必须这样？

因为如果你硬要它都用：

```text
nginx:80 -> backend:8080
```

那么多条连接的四元组就会完全一样：

```text
nginx_ip:80 -> backend_ip:8080
```

而 TCP 不允许多条完全相同四元组的连接同时存在。

所以关键不是“80 不能连后端”。

真正的关键是：_同一个本地 IP:端口，连接同一个远端 IP:端口，不能同时形成很多条完全相同的 TCP 连接_。

## 三、临时端口不是多余的，它是 Nginx 作为客户端时的出口身份

很多人会把这个问题理解成：

> 我不是已经绑定了 80 吗？为什么不能拿这个端口继续出去连？

这句话里真正混淆的，是两种完全不同的职责。

监听端口的职责是：

```text
接收入站连接
```

而临时端口的职责是：

```text
标识这次出站连接的本地身份
```

所以更准确的理解不是：

```text
我已经有 80 端口了，所以出去也该用 80
```

而是：

```text
80 是入口
临时端口是出口身份
```

前者回答的是：

```text
谁能连我
```

后者回答的是：

```text
我连别人时，我是谁
```

这两个问题，在内核里本来就是两套不同的 socket。

## 四、这样再看 keepalive，问题就顺了

一旦你接受了“反向代理其实是两段连接”，那么 `upstream keepalive` 的复用对象就很清楚了。

它复用的不是：

```text
client -> nginx
```

这一段。

它复用的是：

```text
nginx -> backend
```

这一段 TCP 连接。

如果没有 keepalive，那么很多请求都会反复经历：

```text
建立 TCP 连接
发送 HTTP 请求
接收 HTTP 响应
关闭 TCP 连接
```

也就是：

```text
connect
send request
read response
close
```

请求一多，Nginx 到后端这一段就会不断：

- 申请本地临时端口
- 建立到 backend 的连接
- 关闭连接
- 进入 TIME_WAIT
- 再申请新的临时端口

所以不开复用时，你看到的很多连接开销，其实不一定是后端业务代码本身慢，而是：_Nginx 到 upstream 这一段，在反复建连和关连_。

## 五、upstream keepalive 到底保留了什么？

它保留的是：

_已经建立好、暂时空闲、后面还可以继续拿来用的 upstream TCP 连接_。

也就是说：

不用 keepalive 时：

```text
请求 1 -> 新建连接 -> 关闭
请求 2 -> 新建连接 -> 关闭
请求 3 -> 新建连接 -> 关闭
```

用了 keepalive 时：

```text
请求 1 -> 新建连接 -> 保留
请求 2 -> 复用连接
请求 3 -> 复用连接
```

所以它省掉的不是“请求处理逻辑”。

它省掉的是这几个成本：

- TCP 三次握手
- 频繁分配本地临时端口
- `TIME_WAIT` 堆积
- 内核维护大量短连接的成本
- Nginx 到 backend 这一段反复建连的开销

## 六、一个常见误解：`keepalive 32` 不是最大并发 `32`

很多人看到这段配置：

```nginx
upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;

    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://backend;
    }
}
```

会下意识觉得：

```text
keepalive 32 = 最多只能有 32 条连接
```

这不对。

它更接近的意思是：_每个 worker 进程，最多缓存 32 条空闲的、可复用的 upstream 连接_。

注意这里的关键词是：

```text
空闲
可复用
upstream 连接
```

不是总连接数上限，也不是最大并发请求数上限。

如果有 4 个 worker，那么理论上最多可能保留：`4 * 32 = 128` 条空闲 upstream keepalive 连接。

## 七、为什么这个问题一到压测就特别明显？

因为压测会把连接生命周期的问题放大。

平时请求量不大时，即使你每次都新建：

```text
nginx -> backend
```

这段连接，也不一定马上出事。

但压测一上来，Nginx 就会不断重复：

```text
申请临时端口
connect
转发请求
close
TIME_WAIT
再来一轮
```

这时候你看到的“代理层压力变大”，很多时候不是一个抽象的说法。

它背后真的对应着：

- 更多握手
- 更多短连接
- 更多 TIME_WAIT
- 更多内核连接管理开销

所以有些性能问题，表面上看像“后端扛不住”，实际上真正先抖的地方，可能是：

```text
Nginx -> backend
```

这一段连接管理。

## 最后一句

Nginx 监听 80，只说明它在入口处是服务端；它转发后端时，自己又变成了客户端，所以需要用本地临时端口建立另一段 TCP 连接。upstream keepalive 复用的，也不是客户端连接，而是 Nginx 到后端这一段已经建好的出口连接。
