03-反向代理与负载均衡

1. 阶段定位

第三阶段是 Nginx 从“能写配置”进入“能作为稳定入口代理”的关键阶段。前两阶段已经解决了 Nginx 如何部署、配置如何组织、请求如何匹配 serverlocation。本阶段重点解决下面这些问题:

1
2
3
4
5
6
7
8
一个请求进入 Nginx 后:
-> 如何被代理到后端服务
-> URI、Host、Header 如何传递
-> 多个后端如何负载均衡
-> 后端故障时如何摘除、重试和恢复
-> 超时发生在哪个阶段
-> 长连接、WebSocket、gRPC 如何代理
-> 502、503、504 如何定位

反向代理与负载均衡是 Nginx 在生产环境中最常见、最核心的使用方式。很多公司把 Nginx 放在应用服务、微服务网关、前端静态站点、对象存储、容器入口、Kubernetes Ingress 或 API Gateway 前面,用它承担统一入口、流量转发、负载分摊、故障隔离、Header 标准化和基础日志观测能力。

完成本阶段后,应该具备下面的能力:

  • 能独立配置 Nginx 代理 Spring Boot、Tomcat、Node.js、Go、Python、PHP-FPM、静态前端服务等后端。
  • 能清晰解释 proxy_pass 带 URI 和不带 URI 的转发差异。
  • 能使用 upstream 定义后端集群,并配置轮询、权重、IP 哈希、最少连接和一致性哈希类策略。
  • 能理解 max_failsfail_timeoutproxy_next_upstream 的真实行为和局限。
  • 能合理设置连接超时、发送超时、读取超时,避免误判 502、503、504。
  • 能正确传递 Host、客户端真实 IP、协议、端口、链路追踪 Header。
  • 能配置 upstream keepalive、WebSocket 代理和基本 gRPC 代理。
  • 能根据 access log、error log、upstream 响应时间和后端日志定位代理链路问题。

2. 学习边界

2.1 本阶段重点

本阶段重点关注 Nginx 作为 HTTP 反向代理和七层负载均衡器时的核心配置与生产实践:

  • proxy_pass 的基本用法和 URI 拼接规则。
  • proxy_set_header 的 Header 传递规则。
  • upstream 后端集群定义。
  • 负载均衡算法与适用场景。
  • 后端故障判断、失败计数和重试策略。
  • 连接、发送、读取等代理超时。
  • 客户端真实 IP 与代理链路 Header。
  • upstream keepalive 连接复用。
  • WebSocket、SSE、长轮询等长连接代理。
  • gRPC 与 HTTP/2 代理基础。
  • 502、503、504 常见原因和排查路径。
  • 面向生产的反向代理配置模板。

2.2 本阶段暂不深入

下面内容会在后续阶段系统展开,本阶段只做必要铺垫:

  • HTTPS、证书、TLS 协议、安全套件和 HTTP/2 入口治理。
  • 限流、访问控制、防盗链、安全 Header 和 WAF 类加固。
  • proxy cache、静态资源缓存、gzip、brotli 和性能优化。
  • Prometheus、Grafana、OpenTelemetry、ELK、Loki 等完整观测体系。
  • Nginx 高可用、Keepalived、灰度发布、蓝绿发布和热升级。
  • Nginx upstream 模块、phase handler、filter 链源码实现。
  • OpenResty、Lua、Ingress Controller、Envoy、HAProxy 等相邻技术细节。

本阶段的目标不是背完整指令手册,而是建立稳定的代理链路思维:

1
2
3
4
5
6
7
8
9
客户端
-> Nginx server/location 匹配
-> proxy 模块构造转发请求
-> upstream 选择后端节点
-> 建立或复用后端连接
-> 发送请求头和请求体
-> 等待并读取响应
-> 响应返回客户端
-> 记录访问日志和错误日志

3. 反向代理基础认知

3.1 什么是正向代理

正向代理是代理客户端访问外部服务。客户端知道自己在使用代理,目标服务通常不知道真实客户端是谁,只看到代理服务器。

典型场景:

1
客户端 -> 正向代理 -> 外部网站

常见用途:

  • 内网客户端通过代理访问外网。
  • 开发环境配置 HTTP 代理。
  • 企业网关统一出口。
  • 爬虫或测试流量通过代理出口。

3.2 什么是反向代理

反向代理是代理后端服务对外提供访问入口。客户端通常不知道真实后端服务地址,只知道访问 Nginx 入口。

典型场景:

1
客户端 -> Nginx -> 后端应用

客户端访问的是:

1
http://www.example.com/api/users

Nginx 内部可能转发到:

1
2
3
http://10.0.0.11:8080/users
http://10.0.0.12:8080/users
http://10.0.0.13:8080/users

反向代理的核心价值:

  • 隐藏后端真实地址。
  • 统一入口域名和端口。
  • 对多个后端做负载均衡。
  • 在入口层统一处理 Header、日志、安全和超时。
  • 隔离客户端与后端应用的直接连接。
  • 降低后端服务暴露面。

3.3 Nginx 在代理链路中的位置

生产中常见链路如下:

1
2
3
4
5
6
7
用户浏览器 / App
-> CDN
-> 公网 SLB / ELB / ALB
-> Nginx
-> API Gateway / Spring Cloud Gateway
-> 微服务
-> DB / Redis / MQ

也可能是简化版:

1
2
3
用户浏览器
-> Nginx
-> Spring Boot 应用集群

或者前后端分离场景:

1
2
3
4
5
用户浏览器
-> Nginx
/ \
/ \
静态前端文件 后端 API 集群

Nginx 在这些场景中通常承担:

  • 入口路由。
  • 反向代理。
  • 负载均衡。
  • 静态资源服务。
  • Header 标准化。
  • 日志采集。
  • 基础故障隔离。

4. 最小反向代理配置

4.1 最小配置示例

假设后端应用监听在本机 8080 端口:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name app.example.com;

location / {
proxy_pass http://127.0.0.1:8080;
}
}

访问:

1
curl http://app.example.com/

Nginx 会把请求转发到:

1
http://127.0.0.1:8080/

4.2 代理远程后端

后端不在本机时,可以写远程 IP:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name api.example.com;

location / {
proxy_pass http://10.0.0.11:8080;
}
}

如果后端使用域名:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name api.example.com;

location / {
proxy_pass http://backend.internal.example.com:8080;
}
}

注意:如果 proxy_pass 后面使用变量或依赖运行时 DNS 解析,后续还要理解 resolver。普通固定域名通常在配置加载阶段解析。

4.3 代理到不同路径

例如:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name www.example.com;

location /api/ {
proxy_pass http://127.0.0.1:8080/;
}

location /admin/ {
proxy_pass http://127.0.0.1:9000/;
}
}

含义:

1
2
/api/   -> 8080 后端
/admin/ -> 9000 后端

但这里最容易踩坑的是 proxy_pass 后面是否带 /,下一节重点解释。

5. proxy_pass 核心规则

5.1 proxy_pass 基础语法

常见语法:

1
2
3
4
5
proxy_pass http://127.0.0.1:8080;
proxy_pass http://127.0.0.1:8080/;
proxy_pass http://backend;
proxy_pass http://backend/;
proxy_pass https://backend.example.com;

proxy_pass 最常见的位置是 location。它决定当前请求被转发到哪个上游服务,以及转发时 URI 如何变化。

理解 proxy_pass 时一定要分清两个问题:

1
2
后端地址是谁
转发给后端的 URI 是什么

很多代理问题不是后端不可用,而是 URI 被 Nginx 改成了后端不认识的路径。

5.2 不带 URI 的 proxy_pass

示例:

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080;
}

请求:

1
GET /api/users?id=1

后端收到:

1
GET /api/users?id=1

也就是说,proxy_pass 不带 URI 时,Nginx 通常保留当前请求 URI,/api/ 前缀仍然传给后端。

适用场景:

  • 后端应用本身就有 /api 上下文路径。
  • Nginx 只做透明代理,不负责改路径。
  • Spring Boot、Tomcat、网关服务本身已经配置了 /api 前缀。

5.3 带 URI 的 proxy_pass

示例:

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}

请求:

1
GET /api/users?id=1

后端收到:

1
GET /users?id=1

也就是说,proxy_pass 带 URI / 时,Nginx 会用 proxy_pass 中的 URI 替换匹配到的 location 前缀。

适用场景:

  • 对外暴露 /api/,后端接口实际没有 /api/ 前缀。
  • 前端统一访问 /api/users,后端实际接口是 /users
  • 入口层希望隐藏后端真实上下文路径。

5.4 URI 替换示例表

假设请求是:

1
/api/users/list?status=1
location proxy_pass 后端收到的 URI
location /api/ http://backend /api/users/list?status=1
location /api/ http://backend/ /users/list?status=1
location /api/ http://backend/v1/ /v1/users/list?status=1
location /api http://backend /api/users/list?status=1
location /api http://backend/ 可能形成 //users/list?status=1,容易出现双斜杠问题

推荐写法:

1
2
3
location /api/ {
proxy_pass http://backend/;
}

不推荐在需要目录前缀语义时写成:

1
2
3
location /api {
proxy_pass http://backend/;
}

原因是 /api 前缀可能匹配 /api/api/xxx,甚至容易误伤类似 /apixxx 的路径。更清晰的做法是用 /api/ 表示目录型前缀。

5.5 proxy_pass 与正则 location

正则 location 中使用 proxy_pass 要非常谨慎。

示例:

1
2
3
location ~ ^/api/(.*)$ {
proxy_pass http://backend/$1;
}

这类配置看起来灵活,但容易引入问题:

  • URI 编码处理不一致。
  • 查询参数拼接不清晰。
  • 捕获组为空导致路径异常。
  • 双斜杠问题。
  • 后续维护者很难一眼判断后端实际收到什么路径。

能用普通前缀匹配解决的场景,不建议优先使用正则 location。

更推荐:

1
2
3
location /api/ {
proxy_pass http://backend/;
}

5.6 proxy_pass 与 rewrite

有些配置会先 rewrite 再 proxy:

1
2
3
4
location /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://backend;
}

这类写法可以工作,但可读性通常不如:

1
2
3
location /api/ {
proxy_pass http://backend/;
}

选择建议:

  • 只是去掉固定前缀,优先使用 proxy_pass http://backend/
  • 需要复杂路径改写,再考虑 rewrite
  • rewrite 与 proxy 混用时必须写清楚 lastbreak 的影响。
  • 生产配置中要用日志或后端回显接口验证实际 URI。

6. upstream 基础

6.1 upstream 是什么

upstream 用于定义一组后端服务器,供 proxy_pass 引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

server {
listen 80;
server_name app.example.com;

location / {
proxy_pass http://app_backend;
}
}

请求进入 Nginx 后,会从 app_backend 中选择一个后端节点转发。

6.2 upstream 所在上下文

upstream 定义在 http 上下文中,不能写在 serverlocation 里面。

正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
http {
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

server {
listen 80;

location / {
proxy_pass http://app_backend;
}
}
}

错误:

1
2
3
4
5
server {
upstream app_backend {
server 10.0.0.11:8080;
}
}

配置检查会报类似错误:

1
"upstream" directive is not allowed here

6.3 upstream 命名建议

命名要表达业务含义,不要只写 backendserver1

推荐:

1
2
3
4
5
6
7
8
9
upstream user_service {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

upstream order_service {
server 10.0.0.21:8080;
server 10.0.0.22:8080;
}

不推荐:

1
2
3
upstream backend {
server 10.0.0.11:8080;
}

原因:

  • 多个系统共用 Nginx 时容易混淆。
  • access log 中记录 upstream 名称时不利于排查。
  • 配置拆分后不容易判断用途。

6.4 upstream server 常见参数

示例:

1
2
3
4
5
upstream app_backend {
server 10.0.0.11:8080 weight=3 max_fails=2 fail_timeout=10s;
server 10.0.0.12:8080 weight=1 max_fails=2 fail_timeout=10s;
server 10.0.0.13:8080 backup;
}

常见参数:

参数 含义
weight 权重,权重越高分配到的请求越多
max_fails fail_timeout 时间内允许失败的次数
fail_timeout 失败统计窗口,也是临时不可用时间
backup 备用节点,主节点不可用时才使用
down 标记节点不可用,常用于临时下线
max_conns 限制到该后端的最大连接数,需注意版本和模块支持

6.5 upstream 与 server/location 的关系

可以理解为:

1
2
3
server/location 负责匹配入口请求
upstream 负责定义后端节点池
proxy_pass 负责把入口和后端节点池连接起来

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
upstream user_service {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

upstream order_service {
server 10.0.0.21:8080;
server 10.0.0.22:8080;
}

server {
listen 80;
server_name api.example.com;

location /users/ {
proxy_pass http://user_service/;
}

location /orders/ {
proxy_pass http://order_service/;
}
}

这种写法可以把不同路径分发到不同后端服务。

7. 负载均衡算法

7.1 round-robin 轮询

默认算法就是轮询,不需要额外配置。

1
2
3
4
5
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080;
}

请求大致按顺序分配到不同节点。

适用场景:

  • 后端机器配置接近。
  • 请求处理耗时相对均匀。
  • 无强会话粘性要求。
  • 大多数普通 HTTP API 服务。

注意:轮询不是严格每个节点完全相等。连接状态、失败重试、keepalive、请求耗时都会影响实际分布。

7.2 weight 权重

权重用于控制不同节点接收请求的比例。

1
2
3
4
upstream app_backend {
server 10.0.0.11:8080 weight=3;
server 10.0.0.12:8080 weight=1;
}

大致含义:

1
2
10.0.0.11 接收约 3 份请求
10.0.0.12 接收约 1 份请求

适用场景:

  • 新旧机器配置不同。
  • 容器实例资源不同。
  • 灰度导流一小部分流量。
  • 临时降低某个节点流量。

灰度示例:

1
2
3
4
upstream app_backend {
server 10.0.0.11:8080 weight=99;
server 10.0.0.12:8080 weight=1;
}

表示大部分流量仍然到旧节点,少量流量到新节点。但这只是非常基础的权重灰度,不能替代完整的灰度发布系统。

7.3 ip_hash

ip_hash 根据客户端 IP 做哈希,同一个客户端 IP 尽量转发到同一个后端。

1
2
3
4
5
upstream app_backend {
ip_hash;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

适用场景:

  • 后端使用本地 Session,不能跨节点共享。
  • 老系统暂时无法改造 Session 存储。
  • 希望同一客户端尽量访问同一实例。

局限:

  • 多个用户经过同一个 NAT 出口时,可能集中到同一个后端。
  • 客户端 IP 变化会导致后端节点变化。
  • 后端节点上下线会影响哈希分布。
  • 不适合大规模动态扩缩容场景。

更推荐的会话方案:

1
2
3
应用无状态化
-> Session 存 Redis / 数据库 / 专用会话存储
-> 任意后端节点都能处理请求

7.4 least_conn 最少连接

least_conn 会优先选择当前活动连接数较少的后端。

1
2
3
4
5
6
upstream app_backend {
least_conn;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080;
}

适用场景:

  • 请求耗时差异较大。
  • 有长连接或慢请求。
  • 后端服务处理时间不均匀。
  • 轮询容易让某些节点积压连接。

注意:最少连接不是最少负载。连接数少不代表 CPU、内存、数据库压力一定小。

7.5 hash

hash 可以根据指定 key 做哈希。

1
2
3
4
5
upstream app_backend {
hash $request_uri;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

也可以使用一致性哈希:

1
2
3
4
5
6
upstream app_backend {
hash $request_uri consistent;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080;
}

适用场景:

  • 需要相同 URI 尽量落到同一后端。
  • 后端有本地缓存,希望提升命中率。
  • 需要按某个请求变量固定路由。

常见 key:

1
2
3
4
hash $request_uri consistent;
hash $remote_addr consistent;
hash $arg_user_id consistent;
hash $cookie_sessionid consistent;

选择 key 时要注意:

  • key 分布是否均匀。
  • key 是否可能为空。
  • key 是否容易被客户端伪造。
  • 后端扩缩容时是否能接受重新分布。

7.6 算法选择建议

场景 推荐算法
普通无状态 API 默认轮询
节点配置不同 weight
老系统本地 Session ip_hash,但建议尽快改造
请求耗时差异大 least_conn
需要按 URI 或用户固定路由 hash ... consistent
简单灰度少量导流 weight
大规模灰度发布 结合网关、发布系统或服务治理平台

8. Header 传递

8.1 为什么要设置 Header

Nginx 代理后,后端看到的直接客户端通常是 Nginx,而不是真实用户。

如果不传递必要 Header,后端可能出现:

  • 获取到的客户端 IP 是 Nginx IP。
  • 生成回调地址时 Host 不正确。
  • 判断 HTTP/HTTPS 协议错误。
  • 登录、OAuth、SSO 回调地址错误。
  • 后端审计日志缺少真实来源。
  • 链路追踪信息丢失。

因此生产代理配置通常要显式设置代理 Header。

8.2 Host

常见配置:

1
proxy_set_header Host $host;

含义:把客户端请求的 Host 传给后端。

也有人使用:

1
proxy_set_header Host $http_host;

两者区别:

变量 含义
$host Nginx 规范化后的主机名,可能来自 Host、server_name 或请求行
$http_host 原始 Host 请求头,可能包含端口

一般建议:

1
proxy_set_header Host $host;

如果后端必须感知原始端口,可以根据场景使用:

1
proxy_set_header Host $http_host;

8.3 X-Real-IP

配置:

1
proxy_set_header X-Real-IP $remote_addr;

含义:把 Nginx 看到的客户端 IP 放到 X-Real-IP

如果 Nginx 前面没有 CDN、SLB、其他代理,那么 $remote_addr 通常就是真实客户端 IP。

如果 Nginx 前面还有代理,$remote_addr 可能是上一层代理的 IP,需要配合 real_ip_headerset_real_ip_from 使用。

8.4 X-Forwarded-For

配置:

1
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

X-Forwarded-For 用于记录代理链路上的客户端 IP。

假设原始请求头:

1
X-Forwarded-For: 1.1.1.1

Nginx 的 $remote_addr 是:

1
2.2.2.2

使用 $proxy_add_x_forwarded_for 后传给后端:

1
X-Forwarded-For: 1.1.1.1, 2.2.2.2

注意:X-Forwarded-For 是普通请求头,客户端可以伪造。生产中不能无条件信任最左侧 IP,必须结合可信代理列表处理。

8.5 X-Forwarded-Proto

配置:

1
proxy_set_header X-Forwarded-Proto $scheme;

含义:告诉后端客户端访问 Nginx 时使用的协议:

1
2
http
https

典型问题:

1
2
3
4
用户通过 HTTPS 访问 Nginx
Nginx 通过 HTTP 转发后端
后端以为自己收到的是 HTTP
生成的跳转地址变成 http://...

此时需要传递:

1
proxy_set_header X-Forwarded-Proto $scheme;

后端框架也要正确开启 forwarded header 识别。

8.6 X-Forwarded-Host 与 X-Forwarded-Port

常见配置:

1
2
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

用于让后端知道原始访问域名和端口。

如果 Nginx 在 HTTPS 入口监听 443,转发到后端 8080,后端默认只能看到 8080。某些框架生成绝对 URL 时会依赖这些 Header。

8.7 推荐代理 Header 模板

常见 HTTP API 代理模板:

1
2
3
4
5
6
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

如果有链路追踪,可以继续传:

1
2
proxy_set_header X-Request-ID $request_id;
proxy_set_header X-Correlation-ID $http_x_correlation_id;

9. 真实客户端 IP

9.1 单层 Nginx 场景

链路:

1
客户端 -> Nginx -> 后端

此时 Nginx 看到的 $remote_addr 通常就是客户端真实 IP。

配置:

1
2
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

后端可读取:

1
2
X-Real-IP
X-Forwarded-For

9.2 多层代理场景

链路:

1
客户端 -> CDN -> SLB -> Nginx -> 后端

此时 Nginx 看到的 $remote_addr 可能是 SLB、CDN 回源节点或上一层代理的 IP,而不是真实客户端。

可以配置:

1
2
3
4
5
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

含义:

  • set_real_ip_from 指定可信代理 IP 段。
  • real_ip_header 指定从哪个 Header 取真实 IP。
  • real_ip_recursive on 在多级代理中递归寻找第一个非可信代理 IP。

不要写成:

1
set_real_ip_from 0.0.0.0/0;

这样等于信任任意客户端伪造的 X-Forwarded-For,有明显安全风险。

9.3 后端框架读取真实 IP

不同后端框架处理方式不同:

后端 常见处理
Spring Boot 配置 server.forward-headers-strategy 或使用容器相关 Forwarded Header 支持
Tomcat 配置 RemoteIpValve
Node.js Express 配置 trust proxy
Go 手动读取并校验 Header,或使用可信代理中间件
Django 配置 SECURE_PROXY_SSL_HEADER 等相关项

核心原则:

1
只有来自可信代理的 X-Forwarded-For 才能被后端当作真实 IP

9.4 真实 IP 排查方法

临时加一个调试 location:

1
2
3
4
location = /debug-ip {
default_type text/plain;
return 200 "remote_addr=$remote_addr\nrealip_remote_addr=$realip_remote_addr\nx_forwarded_for=$http_x_forwarded_for\n";
}

访问:

1
curl -H "X-Forwarded-For: 1.1.1.1" http://example.com/debug-ip

观察返回值,判断 Nginx 是否信任了正确代理层。

10. 代理超时

10.1 超时为什么重要

反向代理链路中至少有三段关键时间:

1
2
3
Nginx 连接后端的时间
Nginx 向后端发送请求的时间
Nginx 等待后端响应的时间

如果超时设置不合理,会出现:

  • 后端只是慢,Nginx 却提前返回 504。
  • 后端已经挂了,Nginx 却长时间占用连接等待。
  • 大请求上传时被误断开。
  • 长连接接口被当成普通短请求切断。
  • 用户体验、后端资源和 Nginx 连接资源全部受影响。

10.2 proxy_connect_timeout

配置:

1
proxy_connect_timeout 3s;

含义:Nginx 与后端建立 TCP 连接的超时时间。

发生阶段:

1
Nginx -> backend TCP connect

常见原因:

  • 后端端口未监听。
  • 后端机器宕机。
  • 防火墙阻断。
  • 网络路由异常。
  • 后端连接队列满。

配置建议:

1
proxy_connect_timeout 2s;

或:

1
proxy_connect_timeout 3s;

一般不建议设置太长。连接后端都连接不上,通常应该快速失败或尝试其他节点。

10.3 proxy_send_timeout

配置:

1
proxy_send_timeout 30s;

含义:Nginx 向后端发送请求的超时时间。更准确地说,它控制两次成功写操作之间的超时,不是整个请求体发送完成的总耗时。

常见影响场景:

  • 大文件上传。
  • 客户端请求体很大。
  • 后端读取请求体很慢。
  • 后端网络拥塞。

普通 API 可以设置:

1
proxy_send_timeout 30s;

上传接口可能需要更长:

1
proxy_send_timeout 300s;

10.4 proxy_read_timeout

配置:

1
proxy_read_timeout 60s;

含义:Nginx 等待后端响应数据的超时时间。它也是两次成功读操作之间的超时,不是整个响应下载完成的总时间。

常见影响场景:

  • 后端接口处理慢。
  • 数据库查询慢。
  • 后端调用第三方接口慢。
  • WebSocket、SSE、长轮询连接。
  • 大文件下载或流式响应。

普通 API 示例:

1
proxy_read_timeout 60s;

长轮询或 WebSocket 示例:

1
proxy_read_timeout 3600s;

10.5 超时参数对比表

指令 发生阶段 常见错误 调整思路
proxy_connect_timeout 连接后端 502/504,具体看日志 一般较短,2-5 秒
proxy_send_timeout 向后端发送请求 连接关闭或请求发送失败 上传场景适当变长
proxy_read_timeout 等待后端响应 504 慢接口、长连接场景变长

10.6 不要盲目调大超时

很多人遇到 504 的第一反应是:

1
proxy_read_timeout 600s;

这可能掩盖真正问题:

  • SQL 慢查询。
  • 后端线程池耗尽。
  • RPC 调用超时过长。
  • 下游服务不可用。
  • 数据库连接池不足。
  • 接口设计不适合同步等待。

正确思路:

1
2
3
先确认接口是否真的应该执行这么久
再确认后端日志和耗时分布
再决定是优化后端、异步化,还是调大 Nginx 超时

10.7 常见超时模板

普通 API:

1
2
3
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;

上传接口:

1
2
3
4
client_max_body_size 200m;
proxy_connect_timeout 3s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;

WebSocket:

1
2
3
proxy_connect_timeout 3s;
proxy_send_timeout 60s;
proxy_read_timeout 3600s;

后台导出接口:

1
2
3
proxy_connect_timeout 3s;
proxy_send_timeout 60s;
proxy_read_timeout 300s;

生产上要根据业务 SLA、后端耗时分布和调用链超时统一设计,不要让 Nginx 超时明显大于所有下游超时。

11. 故障摘除与失败判断

11.1 max_fails 与 fail_timeout

示例:

1
2
3
4
upstream app_backend {
server 10.0.0.11:8080 max_fails=2 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=2 fail_timeout=10s;
}

含义不是“失败 2 次后永久下线”。更准确地说:

1
2
如果某个后端在 10 秒内失败达到 2 次,Nginx 会在接下来的 10 秒内暂时不选它。
10 秒后,Nginx 会再次尝试该节点。

fail_timeout 有两个作用:

  • 失败统计窗口。
  • 节点被认为不可用后的临时屏蔽时间。

11.2 什么算失败

失败通常与以下情况有关:

  • 连接后端失败。
  • 向后端发送请求失败。
  • 读取后端响应失败。
  • 后端响应超时。
  • 根据 proxy_next_upstream 配置把特定状态码视为可重试失败。

不是所有 HTTP 500 都必然导致节点摘除,具体行为要结合 proxy_next_upstream

11.3 max_fails=0

配置:

1
server 10.0.0.11:8080 max_fails=0;

含义:关闭该节点的失败计数。

不建议普通生产配置随意关闭失败判断,否则故障节点可能持续接收请求。

11.4 单节点 upstream 的特殊性

如果 upstream 只有一个节点:

1
2
3
upstream app_backend {
server 10.0.0.11:8080 max_fails=2 fail_timeout=10s;
}

即使它失败,也没有其他节点可选。用户仍然会看到错误。

因此高可用至少需要:

1
2
3
4
两个以上后端节点
后端健康检查或失败摘除机制
合理的重试策略
统一发布与回滚机制

开源版 Nginx 的主动健康检查能力有限,更多依赖被动失败判断。主动健康检查通常需要 Nginx Plus、第三方模块、OpenResty、服务发现系统或上层负载均衡器支持。

11.5 backup 备用节点

配置:

1
2
3
4
5
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.99:8080 backup;
}

含义:正常情况下使用前两个主节点,主节点都不可用时才使用 backup 节点。

适用场景:

  • 临时灾备节点。
  • 降级服务节点。
  • 只读兜底服务。

注意:备用节点不是灰度节点,不适合用来承接少量常规流量。

11.6 down 临时下线

配置:

1
2
3
4
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080 down;
}

含义:10.0.0.12 暂时不参与负载均衡。

适用场景:

  • 临时摘除故障节点。
  • 发布前手动下线节点。
  • 保留配置但暂不启用。

修改后需要:

1
nginx -t && nginx -s reload

或使用 systemd:

1
nginx -t && systemctl reload nginx

12. 重试策略 proxy_next_upstream

12.1 proxy_next_upstream 是什么

proxy_next_upstream 控制在什么情况下,Nginx 可以把请求转发给下一个 upstream 节点。

示例:

1
proxy_next_upstream error timeout http_502 http_503 http_504;

含义:当当前后端出现连接错误、超时、502、503、504 时,尝试下一个后端。

12.2 常见可选条件

条件 含义
error 与后端建立连接、发送请求、读取响应头时发生错误
timeout 与后端连接、发送、读取时超时
invalid_header 后端返回无效响应头
http_500 后端返回 500
http_502 后端返回 502
http_503 后端返回 503
http_504 后端返回 504
http_403 后端返回 403
http_404 后端返回 404
off 关闭重试

12.3 限制重试次数

示例:

1
2
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;

含义:最多尝试 2 个后端节点。

也可以限制重试总时间:

1
proxy_next_upstream_timeout 10s;

生产建议:

1
2
3
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;

12.4 幂等风险

重试不是总是安全的。

GET 通常相对安全:

1
2
GET /products/1
GET /users/100

但 POST、PUT、PATCH、DELETE 可能有副作用:

1
2
3
POST /orders
POST /payments
DELETE /users/1

如果 Nginx 把一个已经被后端处理但响应中断的 POST 请求重试到另一个节点,可能导致:

  • 重复下单。
  • 重复扣款。
  • 重复创建数据。
  • 状态不一致。

所以对于非幂等请求:

  • 应用层必须设计幂等键。
  • 支付、订单、库存类接口要谨慎重试。
  • 不要简单把所有 500 都加入重试。
  • 不要无限重试。

12.5 请求体已发送后的重试限制

如果 Nginx 已经把请求体发送给后端,再发生错误,通常无法安全切换到下一个后端,尤其是非幂等请求。

可以理解为:

1
2
还没把请求完整交给后端之前,重试相对可控
后端可能已经处理请求之后,重试风险很高

因此重试策略必须结合业务语义,不是越多越好。

12.6 推荐重试模板

普通查询 API:

1
2
3
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 8s;

核心交易 API:

1
2
proxy_next_upstream error timeout;
proxy_next_upstream_tries 1;

或者直接关闭:

1
proxy_next_upstream off;

是否关闭要看业务系统是否具备幂等能力。

13. upstream keepalive 连接复用

13.1 为什么需要 keepalive

没有 upstream keepalive 时,Nginx 到后端可能频繁建立和关闭 TCP 连接。

代价包括:

  • TCP 三次握手开销。
  • TLS 后端连接时还有额外握手开销。
  • 后端 accept 压力增加。
  • TIME_WAIT 增多。
  • 高并发下端口和连接资源消耗明显。

upstream keepalive 允许 Nginx worker 复用到后端的空闲连接,减少重复建连成本。

13.2 基础配置

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 64;
}

server {
listen 80;
server_name app.example.com;

location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}

关键点:

1
2
proxy_http_version 1.1;
proxy_set_header Connection "";

否则后端连接复用可能无法按预期工作。

13.3 keepalive 数值含义

1
keepalive 64;

含义不是总连接数最大为 64,而是每个 worker 进程保留的 upstream 空闲 keepalive 连接数量上限。

如果有 4 个 worker:

1
最多可能保留约 4 * 64 个空闲 upstream keepalive 连接

注意:这不是到后端的最大活动连接数限制。活动连接和空闲连接要分开理解。

13.4 keepalive 适用场景

适合:

  • 高 QPS HTTP API。
  • 后端支持 HTTP/1.1 keep-alive。
  • Nginx 与后端网络稳定。
  • 后端连接建立成本较高。

不适合或要谨慎:

  • 后端连接数非常有限。
  • 后端对长时间空闲连接处理不好。
  • 中间网络设备会悄悄断开空闲连接。
  • 请求量很低,收益不明显。

13.5 keepalive 常见误区

误区一:以为 keepalive 64 是最大并发连接数。

实际它控制的是空闲连接池数量,不是活动连接上限。

误区二:只在 upstream 配置 keepalive,但忘了:

1
2
proxy_http_version 1.1;
proxy_set_header Connection "";

误区三:把 keepalive 设置得极大,导致后端维持大量空闲连接。

建议从较小值开始,例如:

1
keepalive 32;

或:

1
keepalive 64;

再结合后端连接数、Nginx worker 数量和压测结果调整。

14. 请求体与缓冲

14.1 client_max_body_size

上传文件时经常遇到:

1
413 Request Entity Too Large

原因通常是请求体超过 client_max_body_size

配置示例:

1
client_max_body_size 100m;

可以放在 httpserverlocation 中。

上传接口单独放大:

1
2
3
4
location /upload/ {
client_max_body_size 500m;
proxy_pass http://upload_service/;
}

不要全局无限放大,否则会增加入口层被大请求拖垮的风险。

14.2 proxy_request_buffering

默认情况下,Nginx 通常会先接收客户端请求体,再转发给后端。

关闭请求缓冲:

1
proxy_request_buffering off;

含义:Nginx 边接收客户端请求体,边转发给后端。

适用场景:

  • 大文件上传。
  • 流式上传。
  • 希望减少 Nginx 本地磁盘临时文件。

风险:

  • 后端会直接承受慢客户端影响。
  • Nginx 更难在请求体未完整接收前做重试。
  • 后端连接占用时间可能变长。

14.3 proxy_buffering

proxy_buffering 控制响应缓冲。

默认通常是开启:

1
proxy_buffering on;

Nginx 会从后端尽快读取响应,缓存在内存或临时文件中,再发给客户端。

优点:

  • 后端可以更快释放连接。
  • 慢客户端不直接拖慢后端。
  • 有利于保护后端。

关闭:

1
proxy_buffering off;

适用场景:

  • SSE。
  • 流式响应。
  • 实时输出日志。
  • 长时间分块响应。

14.4 proxy_buffers

示例:

1
2
3
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;

常见场景:

  • 后端响应头较大。
  • Set-Cookie 很多。
  • 网关追加了大量 Header。
  • 出现 upstream sent too big header

错误日志可能出现:

1
upstream sent too big header while reading response header from upstream

可以适当调大:

1
2
proxy_buffer_size 32k;
proxy_buffers 8 32k;

但要先确认为什么响应头这么大,例如 Cookie 是否过多、Token 是否过长。

14.5 缓冲参数选择建议

场景 建议
普通 API 保持默认或小幅调整
大文件上传 调整 client_max_body_size,谨慎关闭 proxy_request_buffering
SSE/流式响应 关闭 proxy_buffering
大响应头 调整 proxy_buffer_sizeproxy_buffers
慢客户端很多 保持响应缓冲,保护后端

15. WebSocket 代理

15.1 WebSocket 与普通 HTTP 的区别

WebSocket 初始握手是 HTTP 请求,但随后会升级为长连接。

关键 Header:

1
2
Upgrade: websocket
Connection: Upgrade

如果 Nginx 没有正确透传这些 Header,WebSocket 连接会失败。

15.2 最小 WebSocket 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name ws.example.com;

location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
}

15.3 更通用的 Connection 写法

http 中定义:

1
2
3
4
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

在 location 中使用:

1
2
3
4
5
6
7
8
location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}

这种写法比固定 Connection "Upgrade" 更通用。

15.4 WebSocket upstream 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
upstream ws_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 32;
}

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name ws.example.com;

location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

15.5 WebSocket 常见问题

常见错误:

  • 忘记 proxy_http_version 1.1
  • 忘记传 Upgrade
  • 忘记传 Connection
  • proxy_read_timeout 太短,连接被断开。
  • 中间层 SLB、CDN 不支持或限制 WebSocket。
  • 后端服务没有正确处理路径前缀。

排查命令:

1
2
3
4
5
6
curl -i \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://ws.example.com/ws/

正常握手通常返回:

1
HTTP/1.1 101 Switching Protocols

16. SSE 与长轮询代理

16.1 SSE 特点

SSE 即 Server-Sent Events,服务端通过 HTTP 长连接持续向客户端推送事件。

典型响应头:

1
Content-Type: text/event-stream

SSE 不是 WebSocket,但同样依赖长连接和流式响应。

16.2 SSE 推荐配置

1
2
3
4
5
6
7
8
9
10
11
location /events/ {
proxy_pass http://event_backend;
proxy_http_version 1.1;
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;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
}

关键点:

1
2
proxy_buffering off;
proxy_read_timeout 3600s;

否则事件可能被 Nginx 缓冲,客户端不能及时收到。

16.3 长轮询配置

长轮询通常是后端保持请求一段时间,有消息就返回,没有消息超时返回。

示例:

1
2
3
4
5
location /polling/ {
proxy_pass http://polling_backend;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}

注意 Nginx 超时要略大于后端长轮询等待时间。

例如后端最长等待 60 秒,Nginx 可以设置:

1
proxy_read_timeout 75s;

17. gRPC 与 HTTP/2 代理基础

17.1 gRPC 代理特点

gRPC 基于 HTTP/2。Nginx 代理 gRPC 时通常使用 grpc_pass,不是 proxy_pass

简单链路:

1
gRPC client -> Nginx -> gRPC server

17.2 明文 gRPC 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream grpc_backend {
server 10.0.0.11:50051;
server 10.0.0.12:50051;
}

server {
listen 80 http2;
server_name grpc.example.com;

location / {
grpc_pass grpc://grpc_backend;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

17.3 TLS gRPC 示例

如果后端 gRPC 使用 TLS:

1
2
3
location / {
grpc_pass grpcs://grpc_backend;
}

生产中更常见的是客户端到 Nginx 使用 TLS,Nginx 到后端使用明文内网 gRPC 或双向 TLS,具体取决于安全要求。

17.4 gRPC 超时

常见配置:

1
2
3
grpc_connect_timeout 3s;
grpc_send_timeout 60s;
grpc_read_timeout 60s;

与 HTTP 代理类似,也要区分连接、发送和读取阶段。

17.5 gRPC 本阶段掌握程度

本阶段只要求理解:

  • gRPC 不是普通 HTTP/1.1 代理。
  • gRPC 通常用 grpc_pass
  • gRPC 依赖 HTTP/2。
  • 超时、Header、负载均衡仍然有类似问题。
  • 如果生产中大量使用 gRPC,需要继续深入 HTTP/2、连接复用、流、错误码和客户端负载均衡。

18. HTTPS 后端代理

18.1 代理到 HTTPS 后端

Nginx 可以代理到 HTTPS 后端:

1
2
3
location / {
proxy_pass https://backend.example.com;
}

如果使用 upstream:

1
2
3
4
5
6
7
8
upstream https_backend {
server 10.0.0.11:8443;
server 10.0.0.12:8443;
}

location / {
proxy_pass https://https_backend;
}

18.2 SNI

当后端 HTTPS 依赖 SNI 时,需要:

1
2
proxy_ssl_server_name on;
proxy_ssl_name backend.example.com;

完整示例:

1
2
3
4
5
location / {
proxy_pass https://backend.example.com;
proxy_ssl_server_name on;
proxy_ssl_name backend.example.com;
}

如果不配置,可能出现后端证书不匹配或握手失败。

18.3 是否校验证书

可以开启后端证书校验:

1
2
3
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/certs/ca.pem;
proxy_ssl_verify_depth 2;

内网 HTTPS 后端如果使用自签 CA,应该把内部 CA 证书配置给 Nginx。

不要为了省事长期使用不校验证书的方式,尤其是在跨机房、跨云或零信任网络场景中。

18.4 入口 HTTPS 与后端 HTTPS 的区别

常见模式:

1
客户端 --HTTPS--> Nginx --HTTP--> 后端

或:

1
客户端 --HTTPS--> Nginx --HTTPS--> 后端

前者简单、性能开销较小,适合可信内网。

后者链路更安全,适合高安全要求、跨网络边界、服务网格或零信任场景。

HTTPS 入口配置会在第四阶段详细展开。

19. 日志与 upstream 观测

19.1 为什么默认日志不够

默认 access log 通常只能看到:

  • 客户端 IP。
  • 请求方法和 URI。
  • 响应状态码。
  • 响应大小。
  • User-Agent。

但反向代理排障还需要知道:

  • 转发到了哪个 upstream 地址。
  • upstream 返回了什么状态码。
  • 连接后端花了多久。
  • 后端响应头等待了多久。
  • 整个请求耗时多久。
  • 是否发生了重试。

19.2 推荐 upstream 日志格式

http 中定义:

1
2
3
4
5
6
7
8
9
10
11
log_format upstream_main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'host="$host" request_id="$request_id" '
'request_time=$request_time '
'upstream_addr="$upstream_addr" '
'upstream_status="$upstream_status" '
'upstream_connect_time="$upstream_connect_time" '
'upstream_header_time="$upstream_header_time" '
'upstream_response_time="$upstream_response_time" '
'xff="$http_x_forwarded_for"';

使用:

1
access_log /var/log/nginx/app_access.log upstream_main;

19.3 upstream 变量解释

变量 含义
$upstream_addr 实际连接的 upstream 地址,发生重试时可能有多个
$upstream_status upstream 返回的状态码,重试时可能有多个
$upstream_connect_time 与 upstream 建立连接耗时
$upstream_header_time 从 upstream 接收响应头耗时
$upstream_response_time upstream 完整响应耗时
$request_time Nginx 处理整个请求的总耗时

示例:

1
2
3
upstream_addr="10.0.0.11:8080, 10.0.0.12:8080"
upstream_status="502, 200"
upstream_response_time="0.001, 0.035"

表示第一次请求 10.0.0.11 返回 502,随后重试 10.0.0.12 成功。

19.4 通过日志判断瓶颈

情况一:

1
request_time=5.001 upstream_response_time=5.000

说明主要耗时在后端。

情况二:

1
request_time=10.000 upstream_connect_time=3.000 upstream_status=502

说明连接后端阶段异常,可能是网络、端口、后端连接队列或防火墙问题。

情况三:

1
request_time=60.001 upstream_status=504 upstream_response_time=60.000

说明 Nginx 等后端响应超时,重点查后端慢接口或下游依赖。

情况四:

1
request_time=120.000 upstream_response_time=0.010

说明后端很快返回,但客户端接收慢,或者响应传输阶段耗时长。

19.5 error_log 关键字

常见错误日志关键字:

1
2
3
4
5
6
7
connect() failed
upstream timed out
upstream prematurely closed connection
connection refused
no live upstreams
upstream sent too big header
recv() failed

排查时不要只看 HTTP 状态码,要同时看:

1
access.log + error.log + 后端应用日志 + 系统网络状态

20. 502、503、504 排查

20.1 502 Bad Gateway

502 表示 Nginx 作为网关或代理,从上游拿到了异常响应,或者无法正确完成上游通信。

常见原因:

  • 后端服务未启动。
  • 后端端口未监听。
  • 后端进程崩溃。
  • 后端主动关闭连接。
  • 后端返回了非法 HTTP 响应。
  • Nginx 到后端网络不通。
  • 后端响应头过大。
  • HTTPS 后端握手失败。

常见 error_log:

1
2
3
connect() failed (111: Connection refused) while connecting to upstream
upstream prematurely closed connection while reading response header from upstream
upstream sent too big header while reading response header from upstream

排查命令:

1
2
3
4
curl -v http://10.0.0.11:8080/health
ss -lntp | grep 8080
systemctl status app
journalctl -u app -n 100

如果是容器:

1
2
docker ps
docker logs --tail=100 app

20.2 503 Service Unavailable

503 表示服务不可用。

在 Nginx upstream 场景中常见原因:

  • upstream 没有可用节点。
  • 所有后端都被临时判定失败。
  • 配置了限流或并发限制并返回 503。
  • 后端主动返回 503。
  • 维护页或降级配置返回 503。

常见 error_log:

1
no live upstreams while connecting to upstream

排查重点:

1
2
3
4
5
upstream 节点是否都挂了
max_fails/fail_timeout 是否触发
backup 节点是否可用
是否有限流配置
后端是否主动返回 503

20.3 504 Gateway Timeout

504 表示 Nginx 等待 upstream 超时。

常见原因:

  • 后端接口处理时间超过 proxy_read_timeout
  • 后端连接建立超时。
  • 后端线程池或连接池耗尽。
  • 数据库慢查询。
  • RPC/第三方接口无响应。
  • 网络丢包或拥塞。

常见 error_log:

1
upstream timed out (110: Connection timed out) while reading response header from upstream

排查重点:

1
2
3
4
5
6
proxy_read_timeout 设置
upstream_response_time
后端应用日志耗时
数据库慢查询
线程池/连接池状态
下游依赖超时

20.4 502/503/504 对比表

状态码 直观含义 常见方向
502 后端响应异常或连接异常 后端挂了、端口不通、协议错误、响应头异常
503 当前没有可用服务 upstream 全不可用、限流、维护、后端主动 503
504 等后端超时 慢接口、下游卡住、超时配置过短

20.5 标准排查流程

建议按下面顺序排查:

1
2
3
4
5
6
7
8
1. 看 access.log 的 status、upstream_status、upstream_addr、upstream_response_time
2. 看 error.log 的具体错误关键字
3. 直接 curl 后端 IP:端口/health
4. 登录后端机器看进程和端口
5. 看后端应用日志和慢请求日志
6. 看系统资源:CPU、内存、连接数、文件描述符
7. 看网络:防火墙、路由、丢包、DNS
8. 再决定是否调整 Nginx 超时、重试或 upstream 配置

不要在没有定位原因前直接把所有超时调大。

21. 生产代理配置模板

21.1 普通 HTTP API 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
upstream app_backend {
least_conn;
server 10.0.0.11:8080 weight=1 max_fails=2 fail_timeout=10s;
server 10.0.0.12:8080 weight=1 max_fails=2 fail_timeout=10s;
keepalive 64;
}

server {
listen 80;
server_name api.example.com;

access_log /var/log/nginx/api_access.log upstream_main;
error_log /var/log/nginx/api_error.log warn;

location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";

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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;

proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 8s;
}
}

21.2 前后端分离模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
upstream api_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 64;
}

server {
listen 80;
server_name www.example.com;

root /data/www/frontend;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}

location /api/ {
proxy_pass http://api_backend/;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
}
}

注意这里:

1
2
3
location /api/ {
proxy_pass http://api_backend/;
}

表示对外 /api/users 转发给后端 /users

如果后端也需要 /api/users,应改为:

1
2
3
location /api/ {
proxy_pass http://api_backend;
}

21.3 多服务路由模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
upstream user_service {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

upstream order_service {
server 10.0.0.21:8080;
server 10.0.0.22:8080;
}

upstream file_service {
server 10.0.0.31:8080;
}

server {
listen 80;
server_name api.example.com;

location /users/ {
proxy_pass http://user_service/;
include conf.d/proxy_headers.conf;
}

location /orders/ {
proxy_pass http://order_service/;
include conf.d/proxy_headers.conf;
}

location /files/ {
client_max_body_size 500m;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://file_service/;
include conf.d/proxy_headers.conf;
}
}

可以把通用 Header 抽成:

1
2
3
4
5
6
7
8
9
# conf.d/proxy_headers.conf
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

这样可以减少重复配置。

21.4 WebSocket 与 API 共站点模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
upstream api_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 64;
}

upstream ws_backend {
server 10.0.0.21:8080;
server 10.0.0.22:8080;
}

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name app.example.com;

location /api/ {
proxy_pass http://api_backend/;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_read_timeout 60s;
}

location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

22. 配置拆分建议

22.1 为什么要拆分

反向代理配置一多,很容易出现:

  • Header 重复写几十遍。
  • 超时配置不一致。
  • 日志格式不统一。
  • upstream 与 server 混在一个大文件里。
  • 修改一个服务配置时误伤其他服务。

因此建议按职责拆分。

22.2 推荐目录结构

示例:

1
2
3
4
5
6
7
8
9
10
/etc/nginx/
nginx.conf
conf.d/
log_format.conf
proxy_headers.conf
proxy_timeout.conf
upstream_user_service.conf
upstream_order_service.conf
site_api.example.com.conf
site_www.example.com.conf

22.3 nginx.conf 示例

1
2
3
4
5
6
7
8
http {
include mime.types;
default_type application/octet-stream;

include conf.d/log_format.conf;
include conf.d/upstream_*.conf;
include conf.d/site_*.conf;
}

注意 include 顺序:

1
2
3
先定义 log_format
再定义 upstream
最后定义 server

否则 server 中引用 upstream 或 log_format 时可能找不到。

22.4 proxy_headers.conf

1
2
3
4
5
6
7
8
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

22.5 proxy_timeout.conf

1
2
3
4
5
6
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 8s;

在 location 中引用:

1
2
3
4
5
location /api/ {
proxy_pass http://api_backend/;
include conf.d/proxy_headers.conf;
include conf.d/proxy_timeout.conf;
}

22.6 拆分注意事项

  • 不要把 upstream 写进只会被 server 内 include 的文件。
  • 不要在多个文件中重复定义同名 upstream。
  • 不要让 include 顺序依赖文件系统随机排序,尽量用明确前缀。
  • 通用片段只放通用指令,不要混入业务服务地址。
  • 修改配置后必须执行 nginx -t

23. 后端应用接入要点

23.1 Spring Boot

常见 Nginx 配置:

1
2
3
4
5
6
7
location /api/ {
proxy_pass http://spring_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;
}

如果 Spring Boot 需要识别代理 Header,可配置:

1
server.forward-headers-strategy=framework

或根据 Spring Boot、容器版本使用对应配置。

常见问题:

  • 后端生成跳转地址变成 http
  • 获取客户端 IP 是 Nginx IP。
  • context-path 与 proxy_pass URI 拼接不一致。
  • 上传接口被 Nginx 的 client_max_body_size 拦截。

23.2 Tomcat

Tomcat 常见问题:

  • request.getRemoteAddr() 获取的是 Nginx IP。
  • request.getScheme() 判断为 http
  • 应用生成绝对 URL 错误。

可在 Tomcat 层配置 RemoteIpValve,或由应用框架处理 forwarded headers。

Nginx 侧仍然需要传:

1
2
3
4
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;
proxy_set_header Host $host;

23.3 Node.js / Express

Nginx:

1
2
3
4
5
6
7
location / {
proxy_pass http://node_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;
}

Express 中通常需要:

1
app.set('trust proxy', true)

但生产中更建议配置可信代理范围,而不是无脑信任所有代理 Header。

23.4 Go HTTP 服务

Go 后端通常手动读取 Header:

1
2
3
X-Real-IP
X-Forwarded-For
X-Forwarded-Proto

注意不要直接信任客户端伪造的 X-Forwarded-For。应在 Nginx 或网关层先做可信代理处理,再传给应用。

23.5 静态前端 + API

前端构建产物由 Nginx 提供:

1
2
3
location / {
try_files $uri $uri/ /index.html;
}

API 转发:

1
2
3
location /api/ {
proxy_pass http://api_backend/;
}

重点确认:

  • 前端请求 API 的 baseURL 是 /api 还是完整域名。
  • 后端是否需要 /api 前缀。
  • 浏览器跨域问题是否通过同域代理解决。
  • 前端 history 路由是否正确回退到 index.html

24. DNS 与动态 upstream

24.1 固定 IP upstream

最简单稳定:

1
2
3
4
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}

优点:

  • 行为明确。
  • 排查简单。
  • 不依赖 DNS 运行时解析。

缺点:

  • 后端 IP 变化需要改配置并 reload。
  • 容器动态扩缩容不够灵活。

24.2 域名 upstream

1
2
3
4
upstream app_backend {
server app-1.internal.example.com:8080;
server app-2.internal.example.com:8080;
}

注意:开源 Nginx 对 upstream 中域名解析的动态刷新能力有限,通常不是每次请求都重新解析。后端 IP 动态变化时,要谨慎验证。

24.3 proxy_pass 使用变量

示例:

1
2
3
4
5
6
resolver 10.0.0.2 valid=30s;

location / {
set $backend "app.internal.example.com:8080";
proxy_pass http://$backend;
}

这种方式会涉及运行时 DNS 解析。

风险:

  • 配置更复杂。
  • DNS 故障会影响请求。
  • upstream keepalive、负载均衡能力可能受限。
  • 排查成本上升。

普通生产环境不建议为了“看起来动态”而滥用变量 proxy_pass。服务发现、动态 upstream 和容器编排应结合具体架构设计。

24.4 容器和 Kubernetes 场景

在 Kubernetes 中,Nginx 可能代理到:

1
2
3
4
Service ClusterIP
Headless Service
Ingress Controller
Pod IP

如果是普通 Nginx 部署在集群外,建议优先代理到稳定 Service 或上层负载均衡地址,而不是直接写 Pod IP。

Kubernetes Ingress Controller 属于另一套配置模型,本阶段只需要理解 Nginx 反向代理基础,后续再学习 Ingress 注解和控制器行为会更容易。

25. 实验一:两个后端轮询转发

25.1 实验目标

通过两个简单后端服务验证 Nginx upstream 默认轮询。

目标链路:

1
curl -> Nginx:8088 -> backend1:9001 / backend2:9002

这个实验重点不是后端服务本身,而是观察 Nginx 如何在多个 upstream 节点之间分配请求。

25.2 启动两个后端

可以用 Python 快速模拟。

后端 1:

1
2
3
4
5
6
mkdir -p /tmp/backend1
cat > /tmp/backend1/index.html <<'EOF'
backend1
EOF
cd /tmp/backend1
python3 -m http.server 9001

另一个终端启动后端 2:

1
2
3
4
5
6
mkdir -p /tmp/backend2
cat > /tmp/backend2/index.html <<'EOF'
backend2
EOF
cd /tmp/backend2
python3 -m http.server 9002

如果是 Windows,可以用 PowerShell 或任意本地 HTTP 服务启动两个不同端口,核心是让两个端口返回不同内容。

25.3 配置 Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
upstream demo_backend {
server 127.0.0.1:9001;
server 127.0.0.1:9002;
}

server {
listen 8088;
server_name localhost;

location / {
proxy_pass http://demo_backend;
}
}

检查并加载:

1
2
nginx -t
nginx -s reload

25.4 验证

1
for i in {1..10}; do curl -s http://127.0.0.1:8088/; done

预期看到类似:

1
2
3
4
5
backend1
backend2
backend1
backend2
...

实际分布可能受连接复用、失败状态和请求时序影响,但大体会在两个后端之间分配。

25.5 观察 access log

如果使用 upstream 日志格式,可以重点看:

1
2
3
upstream_addr
upstream_status
upstream_response_time

例如:

1
2
upstream_addr="127.0.0.1:9001" upstream_status="200"
upstream_addr="127.0.0.1:9002" upstream_status="200"

这比只看返回内容更适合生产排查。

26. 实验二:权重分配

26.1 实验目标

验证 weight 对请求分布的影响。

26.2 修改 upstream

1
2
3
4
upstream demo_backend {
server 127.0.0.1:9001 weight=3;
server 127.0.0.1:9002 weight=1;
}

重新加载:

1
nginx -t && nginx -s reload

26.3 验证分布

1
for i in {1..40}; do curl -s http://127.0.0.1:8088/; done | sort | uniq -c

预期大致:

1
2
30 backend1
10 backend2

注意这不是严格数学等分。请求数量越大,比例越接近权重。

26.4 实验结论

weight 适合简单权重分流:

  • 机器配置不同,强机器分配更多流量。
  • 新版本灰度,只给少量流量。
  • 某个节点压力大,临时降低权重。

但它不是完整发布系统,不能替代自动健康检查、灰度策略、回滚控制和监控告警。

27. 实验三:后端故障与摘除

27.1 实验目标

停止一个后端,观察 Nginx 行为和错误日志。

27.2 配置失败参数

1
2
3
4
upstream demo_backend {
server 127.0.0.1:9001 max_fails=1 fail_timeout=10s;
server 127.0.0.1:9002 max_fails=1 fail_timeout=10s;
}

重新加载:

1
nginx -t && nginx -s reload

27.3 停止 backend1

停止监听 9001 的 Python 服务。

验证后端已经不可用:

1
curl -v http://127.0.0.1:9001/

应连接失败。

27.4 访问 Nginx

1
for i in {1..10}; do curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8088/; done

观察 error log:

1
tail -f /var/log/nginx/error.log

可能看到:

1
connect() failed (111: Connection refused) while connecting to upstream

如果另一个节点正常,请求会逐渐转向正常节点。

27.5 恢复 backend1

重新启动 9001 后端,等待 fail_timeout 后再次访问,观察它是否重新接收请求。

27.6 实验结论

这个实验要理解三点:

  • max_failsfail_timeout 是被动失败判断,不是主动健康检查。
  • 节点被临时摘除后,过了 fail_timeout 还会被再次尝试。
  • 如果所有节点都不可用,Nginx 无法凭空恢复服务,只能返回错误。

28. 实验四:proxy_pass URI 差异

28.1 实验目标

验证:

1
2
proxy_pass http://backend;
proxy_pass http://backend/;

对后端 URI 的影响。

28.2 准备回显后端

用 Node.js、Go、Python Flask 都可以。下面是 Python Flask 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, request

app = Flask(__name__)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def echo(path):
return {
'path': request.path,
'query': request.query_string.decode(),
'headers': dict(request.headers),
}

app.run(host='0.0.0.0', port=9010)

启动:

1
python3 app.py

28.3 不带 URI 配置

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:9010;
}

访问:

1
curl http://127.0.0.1:8088/api/users?id=1

后端看到:

1
/api/users

28.4 带 URI 配置

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:9010/;
}

访问:

1
curl http://127.0.0.1:8088/api/users?id=1

后端看到:

1
/users

28.5 实验结论

如果后端接口实际路径是:

1
/users

则使用:

1
2
3
location /api/ {
proxy_pass http://backend/;
}

如果后端接口实际路径是:

1
/api/users

则使用:

1
2
3
location /api/ {
proxy_pass http://backend;
}

上线前必须确认后端实际收到的路径,而不是只凭感觉判断。

29. 实验五:真实 IP 与 Header

29.1 实验目标

让后端打印 Header,验证 Nginx 是否正确传递真实 IP、Host 和协议。

这个实验用于确认:

1
2
3
客户端请求 Header
-> Nginx 处理后的 Header
-> 后端实际收到的 Header

29.2 Nginx 配置

1
2
3
4
5
6
7
8
9
location /headers/ {
proxy_pass http://127.0.0.1:9010/;
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}

29.3 访问验证

1
curl -H "X-Forwarded-For: 1.1.1.1" http://127.0.0.1:8088/headers/

后端应能看到类似:

1
2
3
4
5
X-Real-IP: 127.0.0.1
X-Forwarded-For: 1.1.1.1, 127.0.0.1
X-Forwarded-Proto: http
X-Forwarded-Host: 127.0.0.1
Host: 127.0.0.1

29.4 实验结论

  • X-Real-IP 通常记录 Nginx 识别出的直接客户端 IP。
  • X-Forwarded-For 会追加代理链路。
  • X-Forwarded-Proto 告诉后端入口层协议。
  • 后端不能无条件信任客户端传来的伪造 Header。

30. 实验六:WebSocket 代理

30.1 实验目标

通过 Nginx 代理 WebSocket,并验证 101 Switching Protocols

30.2 准备 WebSocket 后端

可以使用任意 WebSocket 服务。Node.js 示例:

1
2
3
4
5
6
7
8
9
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 9020 });

server.on('connection', function connection(ws) {
ws.send('connected to backend websocket');
ws.on('message', function message(data) {
ws.send('echo: ' + data);
});
});

启动:

1
node ws-server.js

30.3 Nginx 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 8089;
server_name localhost;

location /ws/ {
proxy_pass http://127.0.0.1:9020;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
}

30.4 curl 验证握手

1
2
3
4
5
6
curl -i \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://127.0.0.1:8089/ws/

看到:

1
HTTP/1.1 101 Switching Protocols

说明升级成功。

30.5 常见失败现象

如果返回 400、426、502 或连接很快断开,优先检查:

  • WebSocket 后端是否可直接连接。
  • 是否设置 proxy_http_version 1.1
  • 是否透传 UpgradeConnection
  • location 路径是否与后端 WebSocket 路径一致。
  • 中间层是否支持 WebSocket。

31. 实验七:upstream keepalive

31.1 实验目标

开启 upstream keepalive,观察 Nginx 到后端连接复用。

31.2 配置 keepalive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream keepalive_backend {
server 127.0.0.1:9010;
keepalive 32;
}

server {
listen 8090;
server_name localhost;

location / {
proxy_pass http://keepalive_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}

31.3 观察连接

压测:

1
wrk -t2 -c50 -d30s http://127.0.0.1:8090/

观察连接:

1
ss -ant | grep ':9010'

对比关闭 keepalive 前后的连接建立数量、TIME_WAIT 数量和后端 accept 压力。

31.4 实验结论

  • keepalive 能减少 Nginx 到后端的连接建立开销。
  • keepalive 数值不是最大并发连接数。
  • 后端也要支持并合理配置 keep-alive。
  • keepalive 收益需要结合 QPS、后端连接上限和压测结果判断。

32. 实验八:复现 504

32.1 实验目标

通过设置较短 proxy_read_timeout 复现 504。

32.2 准备慢接口

Python Flask 示例:

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask
import time

app = Flask(__name__)

@app.route('/slow')
def slow():
time.sleep(5)
return 'slow response\n'

app.run(host='0.0.0.0', port=9030)

32.3 Nginx 配置

1
2
3
4
5
6
7
8
9
server {
listen 8091;
server_name localhost;

location / {
proxy_pass http://127.0.0.1:9030;
proxy_read_timeout 2s;
}
}

32.4 访问验证

1
curl -i http://127.0.0.1:8091/slow

预期:

1
HTTP/1.1 504 Gateway Time-out

error_log 中可能看到:

1
upstream timed out (110: Connection timed out) while reading response header from upstream

32.5 修复

把超时改为大于后端处理时间:

1
proxy_read_timeout 10s;

重新加载:

1
nginx -t && nginx -s reload

再次访问应成功。

32.6 实验结论

504 不是简单的“Nginx 坏了”,而是 Nginx 等待 upstream 超时。真实原因可能在:

  • Nginx proxy_read_timeout 设置过短。
  • 后端接口本身耗时过长。
  • 后端依赖数据库、缓存、RPC 或第三方接口阻塞。
  • 后端线程池、连接池或系统资源耗尽。

33. 常用排查命令

33.1 检查 Nginx 配置

1
2
nginx -t
nginx -T

nginx -T 可以打印完整合并后的配置,适合确认 include 后最终生效内容。

33.2 检查端口监听

1
2
ss -lntp
ss -lntp | grep 8080

或:

1
netstat -lntp

33.3 直接访问后端

1
2
curl -v http://10.0.0.11:8080/health
curl -v http://10.0.0.12:8080/health

从 Nginx 所在机器访问后端,比从自己电脑访问更有意义。

33.4 查看 access log

1
tail -f /var/log/nginx/access.log

筛选 5xx:

1
awk '$9 ~ /^5/ {print}' /var/log/nginx/access.log | tail -50

如果使用自定义日志格式,可以筛选 upstream 状态和耗时。

33.5 查看 error log

1
tail -f /var/log/nginx/error.log

搜索 upstream 错误:

1
grep -i "upstream" /var/log/nginx/error.log | tail -50

33.6 查看连接状态

1
ss -ant | awk '{print $1}' | sort | uniq -c

查看到某个后端端口的连接:

1
ss -ant | grep ':8080'

查看 TIME_WAIT:

1
ss -ant state time-wait | wc -l

33.7 抓包

1
tcpdump -i any host 10.0.0.11 and port 8080 -nn

保存抓包:

1
tcpdump -i any host 10.0.0.11 and port 8080 -nn -w upstream.pcap

抓包用于确认:

  • Nginx 是否真的向后端发起连接。
  • TCP 是否握手成功。
  • 后端是否返回数据。
  • 是否存在重传、RST、超时。

34. 常见误区

34.1 混淆 proxy_pass URI 规则

错误理解:

1
proxy_pass 后面有没有 / 都一样

实际差异很大:

1
2
proxy_pass http://backend;
proxy_pass http://backend/;

一个通常保留当前 URI,一个会用 proxy_pass 中的 URI 替换匹配到的 location 前缀。

判断标准不是“配置看起来对不对”,而是后端实际收到的 URI 是否符合预期。

34.2 以为 max_fails 是主动健康检查

max_failsfail_timeout 是被动失败判断,不是定时探测后端健康。

如果没有请求打到某个节点,Nginx 不会主动知道它是否恢复。

需要主动健康检查时,应考虑:

  • Nginx Plus。
  • 第三方健康检查模块。
  • OpenResty 实现健康检查。
  • 上层 SLB / 服务发现 / 注册中心。
  • 应用发布系统配合上下线。

34.3 盲目开启重试

把所有状态码都加入重试:

1
proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404;

可能导致:

  • 业务重复处理。
  • 故障扩散。
  • 错误被掩盖。
  • 排查困难。

尤其是订单、支付、库存、优惠券、账户余额等接口,必须优先考虑幂等设计。

34.4 后端拿不到真实 IP 就只怪 Nginx

真实 IP 需要完整链路配合:

1
2
3
4
CDN / SLB 正确传 Header
-> Nginx 正确信任上一层代理
-> Nginx 正确传给后端
-> 后端框架正确信任代理 Header

任一环节错误都会导致 IP 不准。

34.5 所有 502 都怪 Nginx

502 常常是后端问题暴露在 Nginx 上:

  • 后端拒绝连接。
  • 后端崩溃。
  • 后端返回非法响应。
  • 后端响应头过大。
  • 后端 HTTPS 握手异常。
  • Nginx 到后端网络不通。

Nginx 是报错点,不一定是根因。

34.6 keepalive 设置越大越好

keepalive 太大可能导致后端维持大量空闲连接,反而浪费资源。

应结合:

1
2
3
4
5
6
worker 数量
后端连接上限
QPS
请求耗时
后端 keep-alive 配置
中间网络设备空闲连接超时

34.7 用 ip_hash 解决所有 Session 问题

ip_hash 只是临时缓解方案,不是根本方案。

更好的方案是应用无状态化:

1
2
3
Session / 登录态 / 用户上下文
-> Redis / 数据库 / 统一认证中心 / Token
-> 任意后端节点都能处理请求

34.8 只改 Nginx 不看后端

反向代理问题往往跨越多层:

1
客户端 -> Nginx -> 后端应用 -> 数据库 / RPC / 缓存 / 第三方服务

只看 Nginx 配置,很难定位慢接口、线程池耗尽、数据库阻塞、RPC 雪崩等问题。

34.9 滥用变量 proxy_pass

变量 proxy_pass 看起来灵活:

1
2
set $backend "app.example.com";
proxy_pass http://$backend;

但会引入运行时 DNS、连接复用、负载均衡和排查复杂度问题。

除非确实需要动态代理,否则普通业务更推荐固定 upstream。

35. 阶段练习清单

35.1 必做练习

  • 使用两个后端服务完成 Nginx 轮询负载均衡。
  • 配置 weight 并统计请求分布。
  • 对比 proxy_pass http://backendproxy_pass http://backend/ 的 URI 差异。
  • 配置 HostX-Real-IPX-Forwarded-ForX-Forwarded-Proto 并在后端打印验证。
  • 停止一个后端节点,观察 error log 和 upstream 行为。
  • 配置 proxy_connect_timeoutproxy_send_timeoutproxy_read_timeout 并复现超时。
  • 配置 proxy_next_upstream 并观察重试后的 $upstream_addr$upstream_status
  • 配置 upstream keepalive 并观察连接复用。
  • 配置 WebSocket 代理并验证 101 协议升级。
  • 自定义 upstream access log 格式,记录 upstream 地址、状态码和耗时。

35.2 进阶练习

  • 使用 least_conn 对慢接口做负载均衡测试。
  • 使用 hash $request_uri consistent 验证相同 URI 固定路由。
  • 配置一个前后端分离站点,静态资源走本地,API 走 upstream。
  • 配置一个多服务路由站点,/users//orders//files/ 分别转发到不同后端。
  • 对上传接口单独设置 client_max_body_size 和较长超时。
  • 对 SSE 接口关闭 proxy_buffering 并验证实时输出。
  • 代理 HTTPS 后端并配置 proxy_ssl_server_name
  • 使用 tcpdump 抓取 Nginx 到后端的 TCP 连接。

35.3 生产思考题

  • 如果后端接口平均 50ms,但偶尔 30s,应该调大 Nginx 超时还是优化后端?
  • 如果订单创建接口发生 502,是否应该自动重试?为什么?
  • 如果所有用户经过同一个公司 NAT 出口,使用 ip_hash 会有什么问题?
  • 如果后端扩容后请求分布不均,应该从哪些维度排查?
  • 如果 $upstream_response_time 很短但 $request_time 很长,可能是什么原因?
  • 如果后端拿到的协议总是 HTTP,但用户访问的是 HTTPS,应该检查哪些 Header 和框架配置?
  • 如果 upstream keepalive 开启后后端连接数很高,应该如何评估是否合理?

36. 过关标准

完成本阶段后,应能做到:

  • 能独立编写一个稳定的 HTTP 反向代理配置。
  • 能独立配置一个多节点 upstream,并解释负载均衡分配逻辑。
  • 能解释 proxy_pass 带 URI 和不带 URI 的转发差异。
  • 能根据业务选择轮询、权重、ip_hashleast_connhash 等策略。
  • 能正确传递 Host、真实 IP、协议和代理链路 Header。
  • 能说明 proxy_connect_timeoutproxy_send_timeoutproxy_read_timeout 分别对应哪个阶段。
  • 能解释 max_failsfail_timeout 的被动失败判断机制。
  • 能谨慎配置 proxy_next_upstream,理解幂等风险。
  • 能配置 upstream keepalive,并说明它不是最大并发连接数。
  • 能配置 WebSocket、SSE 等长连接代理。
  • 能通过 access log 和 error log 初步定位 502、503、504。
  • 能把 Nginx 接入常见后端应用,并处理真实 IP、协议、路径前缀等问题。

37. 本阶段推荐配置骨架

下面是一份可作为学习和小型生产场景起点的配置骨架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
http {
log_format upstream_main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'host="$host" request_id="$request_id" '
'request_time=$request_time '
'upstream_addr="$upstream_addr" '
'upstream_status="$upstream_status" '
'upstream_connect_time="$upstream_connect_time" '
'upstream_header_time="$upstream_header_time" '
'upstream_response_time="$upstream_response_time" '
'xff="$http_x_forwarded_for"';

upstream app_backend {
least_conn;
server 10.0.0.11:8080 max_fails=2 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=2 fail_timeout=10s;
keepalive 64;
}

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name app.example.com;

access_log /var/log/nginx/app_access.log upstream_main;
error_log /var/log/nginx/app_error.log warn;

location /api/ {
proxy_pass http://app_backend/;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 8s;
}

location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
}

使用前必须根据实际情况调整:

  • server_name
  • upstream 后端 IP 和端口。
  • /api/ 是否需要去掉前缀。
  • 超时时间。
  • 是否需要 WebSocket。
  • 是否需要 HTTPS。
  • 日志路径。
  • 后端是否支持 keepalive。

38. 本阶段总结

第三阶段的核心不是记住某几个配置项,而是建立完整代理链路模型:

1
2
3
4
5
6
7
8
9
10
入口匹配
-> 路径转换
-> Header 标准化
-> upstream 选址
-> 连接后端
-> 请求发送
-> 响应读取
-> 失败重试
-> 日志记录
-> 故障排查

学习反向代理时,最重要的几个判断是:

  • 请求是否进入了正确的 serverlocation
  • 后端实际收到的 URI 是否符合预期。
  • 后端实际收到的 Host、IP、协议 Header 是否符合预期。
  • Nginx 选中了哪个 upstream 节点。
  • 失败发生在连接、发送、读取还是后端处理阶段。
  • 发生重试时是否存在业务幂等风险。
  • 超时是 Nginx 设置问题,还是后端和下游链路问题。

掌握本阶段后,就可以把 Nginx 作为比较稳定的应用入口代理使用。下一阶段建议进入 HTTPS、安全与访问治理,继续补齐入口层安全能力。