Nginx反向代理与负载均衡:从入门到生产最佳实践

读完这篇,你不仅能搭一个能用的反向代理,还能看懂生产环境上千行的 nginx 配置为什么那样写。


一、开篇:反向代理到底解决了什么问题

你大概率遇到过这些场景:

  • 后端服务只监听 127.0.0.1:8080,外网用户怎么访问?
  • 一台机器扛不住了,加了两台,前端路由怎么分?
  • 前端调后端跨域被浏览器拦住,怎么优雅解决?
  • WebSocket / SSE 长连接频繁断,Nginx 上该配什么?
  • 某个接口被脚本疯狂刷,怎么在 Nginx 层拦住?

反向代理 就是这些问题的标准答案。它站在客户端和后端服务器之间,代替后端接收请求再转发,同时顺手把负载均衡、SSL 终结、缓存、限流、日志统一做了。

本文从最基础的 proxy_pass 讲起,一路深入到生产环境跑了三年的真实配置------里面会有你见过的和没见过的写法,每一段都有解释。


二、核心概念:一个请求的完整旅程

复制代码
用户浏览器
    │
    ▼
┌─────────────┐
│    Nginx    │  ← 反向代理(对用户来说是服务器)
│  18081端口   │
└──────┬──────┘
       │ proxy_pass
       ▼
┌─────────────────────────────┐
│  upstream 后端集群           │
│  172.16.130.43:8090  (wg)  │
│  172.16.130.44:8090  (wg)  │
│  172.16.130.48:8090  (警察) │
│  ...                        │
└─────────────────────────────┘

Nginx 在这中间做了什么?

  • 接收请求 --- 监听端口,解析 HTTP 协议
  • 路由决策 --- 根据 location 匹配规则,决定把请求发给哪个 upstream
  • 请求改写 --- 修改 URI(rewrite)、添加/修改请求头(proxy_set_header)
  • 负载均衡 --- 从 upstream 中选一台健康的机器
  • 转发 & 等待 --- 建立连接、发送请求、等待响应
  • 响应返回 --- 拿到后端响应,按需修改响应头,返回给客户端

以上六步,每一步在生产环境都有坑。我们一步步看。


三、基础配置:从 proxy_pass 开始

1. 最简反向代理

nginx 复制代码
server {
    listen 18081;
    server_name _;

    location /api {
        proxy_pass http://172.16.130.43:8090;
    }
}

请求 http://nginx:18081/api/ykz/login → 转发到 http://172.16.130.43:8090/api/ykz/login

但这还不够------后端服务看到的来源 IP 永远是 Nginx 的 IP。需要 IP 透传

2. IP 透传三件套

nginx 复制代码
proxy_set_header X-Real-IP       $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host            $host;
作用 不配的后果
X-Real-IP 真实客户端 IP 后端日志全是 Nginx IP
X-Forwarded-For 代理链完整 IP 多层代理时丢失来源
Host 原始请求域名 后端做域名路由时失效

nginx.conflog_format 通常第一个字段就是 $http_x_real_ip,说明日志体系强依赖这个头。丢了它,全链路追踪就断了。

3. location 匹配优先级

Nginx 的 location 匹配是有严格顺序的,不是你写哪行就先用哪行:

优先级 语法 含义 生产示例
最高 = /path 精确匹配 location = /nginx_status
^~ /prefix 前缀匹配(优先) location ^~ /api/bigScreen/queryBuildingLocation
~ 正则 大小写敏感正则 `location ~ ^/(app
~* 正则 大小写不敏感正则 `location ~* .(?:htm
最低 /prefix 普通前缀匹配 location /api

生产案例解读

nginx 复制代码
# 高优先级前缀匹配:精确拦截特定接口做限流
location ^~ /api/bigScreen/queryBuildingLocation {
    limit_req zone=allips6 burst=120 nodelay;
    proxy_pass http://houseFormPolice;
}

# 正则匹配:处理多路径前缀(app/ 和 bigscreen/ 都要剥离后转发)
location ~ ^/(app/|bigscreen/)?event/insert1 {
    proxy_pass http://172.16.130.56:8090;
    rewrite "^/(app/|bigscreen/)?(.*)$" /$2 break;
}

核心原则 :能用 ^~ 就不要用 ~。正则每次请求都要编译执行,^~ 是纯字符串前缀匹配,性能好得多。

4. 请求头那些事

nginx 复制代码
# 允许带下划线的自定义头(如 my_api_key)
underscores_in_headers on;

# 隐藏 Nginx 版本号(安全基线)
server_tokens off;

# 自定义请求头覆盖
proxy_set_header X-Forwarded-For "";  # 清空上游传过来的伪造头
proxy_set_header Host "23.210.227.114";  # 强制指定 Host(绕过 WAF 拦截)

生产案例 :配置里 underscores_in_headers on 配合 map $http_x_real_ip------真实来源 IP 通过下划线头 X-Real-IP 传递,不开这个选项的话这个头会被 Nginx 直接丢弃。


四、负载均衡实战

1. 四种分配策略

nginx 复制代码
upstream wg {
    # 策略1:加权轮询(默认,也是最常用的)------5台机器均匀分担
    server 172.16.130.43:8090 weight=5 max_fails=5 fail_timeout=30s;
    server 172.16.130.44:8090 weight=5 max_fails=5 fail_timeout=30s;
    server 172.16.130.45:8090 weight=5 max_fails=5 fail_timeout=30s;
    server 172.16.130.46:8090 weight=5 max_fails=5 fail_timeout=30s;
    server 172.16.130.47:8090 weight=5 max_fails=5 fail_timeout=30s;
}

upstream userlogin {
    # 策略2:同样加权轮询,但只有2台------登录接口需要会话保持的话要考虑 ip_hash
    server 172.16.130.30:8090 weight=5 max_fails=3 fail_timeout=5s;
    server 172.16.130.31:8090 weight=5 max_fails=3 fail_timeout=5s;
}
策略 指令 适用场景
加权轮询(默认) weight=N 后端性能不均,通用场景
IP Hash ip_hash; 需要会话保持(如登录态)
最少连接 least_conn; 长连接服务(WebSocket/SSE)
随机 random; 大规模集群,避免热点

2. 健康检查------生产级别的分敏感度策略

生产配置里有一个非常值得学习的模式:不同 upstream 用不同的 fail_timeout

nginx 复制代码
# 核心业务(wg):5次失败才摘除,摘除 30 秒------容错性高
upstream wg {
    server 172.16.130.43:8090 max_fails=5 fail_timeout=30s;
}

# 非核心但需要快速响应的(bigscreen):3次失败即摘除,只摘 5 秒
upstream bigscreen {
    server 172.16.130.8:8090 max_fails=3 fail_timeout=5s;
}

设计思路

  • 核心业务 fail_timeout=30s:给故障恢复留足时间,避免频繁摘除/恢复引起的抖动
  • 大屏接口 fail_timeout=5s:大屏是实时展示,点状的短暂故障可以容忍,5秒后立刻重试即可

建议 :给 userlogin 这样的登录 upstream 加上 ip_hash,避免用户登录后请求落到不同机器导致 session 丢失:

nginx 复制代码
upstream userlogin {
    ip_hash;
    server 172.16.130.30:8090 max_fails=3 fail_timeout=5s;
    server 172.16.130.31:8090 max_fails=3 fail_timeout=5s;
}

3. rewrite 前缀剥离:break vs last

配置里大量使用 rewrite 做路径前缀剥离,这是 Nginx 反向代理最常见的模式之一:

nginx 复制代码
# 模式:URL 前缀剥离
# 请求 /bigscreen/api/ykz → 转发到 bigscreen upstream 的 /api/ykz
location /bigscreen/api {
    proxy_pass http://bigscreen;
    rewrite "^/bigscreen/(.*)$" /$1 break;
}
Flag 行为 什么时候用
break 停止当前 location 的后续 rewrite,继续处理 剥离前缀后必须用 break
last 停止当前 location,用新 URI 重新走一轮 location 匹配 需要换一个 location 处理时
redirect 返回 302 重定向给客户端 需要客户端重新发请求时

常见坑breaklast 搞反了------用 last 做前缀剥离,结果新 URI 匹配到了别的 location,请求被发到了错误的 upstream。

一个需要 last 的生产案例:

nginx 复制代码
# 定义 map 匹配来自23.210.255.100得接口
map $http_x_real_ip $yitu_proxy {
    default             "";
    "23.210.255.100"    "172.16.130.73:18082";
} 
server {
# 翼图 IP 过来的请求,先改写 URI 然后换到 internal location 处理
if ($yitu_proxy != "") {
    rewrite ^/(.*)$ /__yitu_proxy__/$1 last;
}

# internal location:外部无法直接访问,只有 rewrite 进来的才会走到这里
location /__yitu_proxy__/ {
    internal;
    proxy_pass http://$yitu_proxy;
    rewrite "^/__yitu_proxy__/(.*)$" /$1 break;
}
}

这里用 last 是因为改写了 URI 后需要换一个 location 处理;内部 location 里的 break 则是正常的剥离前缀。

4. 接口限流:保护后端的最后一道防线

生产配置里已经有一套成熟的限流体系:

nginx 复制代码
# 定义限流规则
limit_req_zone $binary_remote_addr zone=allips:10m rate=50r/m;   
location ^~ /api/bigScreen/queryBuildingLocation {
    limit_req zone=allips burst=120 nodelay;
    proxy_pass http://houseFormPolice;
}

三个参数的含义

参数 含义 建议
rate=50r/m 每分钟最多50 个请求(约 0.13 req/s) 根据业务需求
burst=120 允许瞬时突发 120 个请求排队 根据业务峰值设,大屏接口设高
nodelay 排队中的请求立即处理(不延迟) 用户体验好,但瞬时压力大

目的:防止突发流量或异常请求瞬间耗尽服务器资源,导致服务宕机。限流相当于给系统装上"安全阀",确保在高压下系统仍然能正常运行,而不是彻底崩溃。

提示$binary_remote_addr$remote_addr 省内存(4字节 vs 7-15字节),大规模限流场景必用。


五、生产最佳实践

1. 全局性能优化

nginx.conf 里有几行容易被忽略但极其重要:

nginx 复制代码
worker_processes auto;           # 自动匹配 CPU 核心数
worker_cpu_affinity auto;        # 每个 worker 绑定一个 CPU 核心
worker_connections 8192;         # 每个 worker 最大连接数

events {
    use epoll;                   # Linux 下最高效的事件模型
    multi_accept on;             # 一次接受所有新连接(而不是一个一个来)
}

http {
    sendfile on;                 # 零拷贝发送静态文件
    tcp_nopush on;               # 配合 sendfile,攒够一个包再发
    tcp_nodelay on;              # 对 keepalive 连接,有数据立即发(降低延迟)
}

这三兄弟 sendfile + tcp_nopush + tcp_nodelay 的组合很多人配错:

  • tcp_nopush on 只在 sendfile on 时生效------它会等数据包填满再发,减少网络包数量
  • tcp_nodelay on 跟它不矛盾------只对 keepalive 连接生效,让小块数据立即发送

2. 日志即监控

生产 log_format 是含金量极高的一行:

nginx 复制代码
log_format main '$http_x_real_ip | $remote_addr | $remote_user | [$time_iso8601] '
                '| "$request" | $status | $body_bytes_sent | $request_time '
                '| $upstream_addr | $upstream_status | $upstream_response_time '
                '| "$http_referer" | "$http_user_agent" | "$http_x_forwarded_for"';

每个字段都是排坑利器

字段 排坑场景
$request_time 客户端感知的响应时间(含网络)
$upstream_response_time 后端真实处理时间------排坑核心指标
$upstream_addr 请求落到了哪台机器------定位单机故障
$upstream_status 后端返回的状态码------502/504 一眼看到
$http_x_real_ip 真实客户端 IP(经过多层代理透传)

经验 :如果 $request_time 很大但 $upstream_response_time 很小,问题在网络层(比如跨机房);如果两者都大,问题在后端。

3. 超时控制------按业务设,不要一刀切

nginx 复制代码
# 全局默认(http 块)
proxy_connect_timeout 30;    # 连接后端超时 30s
proxy_read_timeout   200;    # 等待后端响应超时 200s
proxy_send_timeout   200;    # 发送请求给后端超时 200s

# 特定接口覆盖(server/location 块)
location /chat/ {
    proxy_read_timeout 86400s;   # AI 对话:等 24 小时(SSE 长连接)
}

location /event/getEventByList {
    proxy_read_timeout    60s;   # 普通查询:60s 足够
    proxy_connect_timeout 60s;
    proxy_send_timeout    60s;
}

经验教训 :不要全局设一个很大的 proxy_read_timeout。正常的 API 调 200s 已经是"后端可能挂了"的信号。只有 SSE / WebSocket / AI 推理这种场景才需要 86400s(24小时)。

4. Buffer 调优------两套策略

生产配置恰好展示了两种典型场景的 Buffer 设置:

策略 A:大 Buffer(常规 API / 大响应体)

nginx 复制代码
proxy_buffer_size          1024k;   # 响应头缓冲区
proxy_buffers            16 1024k;  # 16个响应体缓冲区,每个 1MB
proxy_busy_buffers_size   2048k;    # 忙碌时能发送的最大缓冲区
proxy_temp_file_write_size 2048k;   # 写入临时文件的最大块大小

适用场景:后端返回较大 JSON、Excel 导出、图片/文件。允许 Nginx 缓存整个响应后再发给客户端,减少后端连接占用时间。

策略 B:关闭 Buffer(SSE / AI 流式 / MCP 长连接)

nginx 复制代码
location /tuowei/ai-event-api/analysis/chat {
    proxy_buffering off;            # 核心:禁用缓冲,边收边发
    proxy_cache off;                # 关闭缓存
    proxy_http_version 1.1;         # HTTP/1.1 才支持分块传输
    proxy_set_header Connection ''; # 维持长连接,不清空 Connection 头
    chunked_transfer_encoding on;   # 分块传输编码
    proxy_request_buffering off;    # 关闭请求缓冲
    gzip off;                       # 不要压缩流式内容

    # 禁用客户端/中间代理缓冲
    add_header Cache-Control "no-cache, no-transform" always;
    add_header X-Accel-Buffering "no" always;

    # 超长超时(AI 推理可能很久)
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

适用场景:ChatGPT 式流式输出、SSE 推送、MCP 协议长连接。核心就是 proxy_buffering off------它让 Nginx 不缓存后端响应,收到一个 chunk 就立刻发给客户端。

生产案例: MCP 配置也是这样:

nginx 复制代码
location ^~ /jczz_mcp {
    proxy_pass http://172.16.130.152:8000;
    proxy_buffering off;
    proxy_cache off;
    proxy_http_version 1.1;
    chunked_transfer_encoding on;
    proxy_set_header Connection '';
    proxy_read_timeout 86400s;
}

5. WebSocket / SSE 代理

nginx 复制代码
location /api {
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host       $host;
    proxy_pass http://wg;
}

三行关键配置:

  1. proxy_http_version 1.1 --- HTTP/1.1 才支持协议升级
  2. Upgrade $http_upgrade --- 透传客户端的协议升级请求
  3. Connection "Upgrade" --- 告诉后端:这个连接要升级

这也是配置里 /api/bigscreen/api 都在用的模式------因为后端可能同时处理 HTTP API 和 WebSocket,不主动升级就只当 HTTP 走。

6. Gzip 压缩------省带宽的正确姿势

nginx 复制代码
gzip on;
gzip_comp_level 5;          # 压缩级别 5(性价比最佳,不要设 9)
gzip_min_length 1024;       # 小于 1KB 不压缩(越压越大)
gzip_proxied any;           # 对所有代理请求生效(包括后端返回的)
gzip_vary on;               # 告诉缓存服务器"我可能不压缩,别把压缩版发给不支持的用户"
gzip_types text/plain text/css application/javascript application/json
           application/xml image/svg+xml application/octet-stream;

注意 gzip_types 里不要加图片和视频------它们本身就是压缩格式,Gzip 只会让它们更大。

7. 接口下线的正确姿势

配置里有从"正常代理 → 下线 → 拒绝访问"的完整生命周期:

nginx 复制代码
# 阶段1:正常代理
location ^~ /dubbo/api/ {
    proxy_pass http://23.211.10.6:32080;
}

# 阶段2:下线但仍可被调试
# location ^~ /dubbo/api/ {
#     proxy_pass http://23.211.10.6:32080;
# }

# 阶段3:返回 404(彻底下线)
location ^~ /dubbo/api/ {
    return 404;
}

# 阶段4:返回 401(需要鉴权才能访问)
location ^~ /contradiction/insertConfig {
    return 401;
}

经验 :不要直接删 location。先注释掉 proxy_pass 换成 return 404,观察几天日志有没有人还在调,确认无影响后再删除配置。

8. stub_status 监控

nginx 复制代码
location /nginx_status {
    stub_status on;
    access_log off;
    allow 172.16.130.2;    # 仅允许内网监控机器访问
    deny all;
}

访问后返回:

复制代码
Active connections: 291
server accepts handled requests
 16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
字段 含义
Active connections 当前活跃连接数
accepts 历史总连接数
handled 成功处理的连接数(accepts - handled = 连接被拒绝数)
requests 总请求数
Reading / Writing / Waiting 正在读请求头 / 正在写响应 / 空闲 keepalive

9. 生产级完整配置模板

整合以上所有实践,一个可直接放到 conf.d/ 下用的模板:

nginx 复制代码
# ============================================
# 全局设置(放在 nginx.conf 的 http 块)
# ============================================
worker_processes auto;
worker_cpu_affinity auto;
worker_connections 8192;

events {
    use epoll;
    multi_accept on;
}

http {
    server_tokens off;
    underscores_in_headers on;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # 全局超时
    proxy_connect_timeout 30;
    proxy_read_timeout 200;
    proxy_send_timeout 200;

    # 全局日志格式
    log_format main '$http_x_real_ip | $remote_addr | [$time_iso8601] '
                    '| "$request" | $status | $request_time '
                    '| $upstream_addr | $upstream_response_time';

    # 全局 Buffer(大响应体)
    proxy_buffer_size 1024k;
    proxy_buffers 16 1024k;
    proxy_busy_buffers_size 2048k;

    # Gzip
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_vary on;
    gzip_types text/plain text/css application/javascript application/json;

    # ========================================
    # upstream 定义(按业务分池)
    # ========================================
    upstream wg {
        server 172.16.130.43:8090 weight=5 max_fails=5 fail_timeout=30s;
        server 172.16.130.44:8090 weight=5 max_fails=5 fail_timeout=30s;
        server 172.16.130.45:8090 weight=5 max_fails=5 fail_timeout=30s;
    }

    upstream userlogin {
        ip_hash;    # 登录接口必须会话保持
        server 172.16.130.30:8090 weight=5 max_fails=3 fail_timeout=5s;
        server 172.16.130.31:8090 weight=5 max_fails=3 fail_timeout=5s;
    }

    # ========================================
    # 限流规则
    # ========================================
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=120r/m;
    limit_req_zone $binary_remote_addr zone=token_limit:10m rate=8r/m;

    # ========================================
    # server 块
    # ========================================
    include conf.d/*.conf;
}
nginx 复制代码
# conf.d/app.conf ------ 你的实际业务配置
server {
    listen 18081;

    # IP 透传
    proxy_set_header X-Real-IP       $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host            $host;

    # CORS(按需)
    add_header Access-Control-Allow-Origin 'https://your-frontend.com';

    # 静态文件
    location / {
        root /data/web/dist;
        if ($request_filename ~* .*\.(?:htm|html)$) {
            add_header Cache-Control no-store;
        }
    }

    # 常规 API → 负载均衡
    location /api {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass http://wg;
    }

    # 大屏 API(路径前缀剥离)
    location /bigscreen/api {
        proxy_pass http://bigscreen;
        rewrite "^/bigscreen/(.*)$" /$1 break;
    }

    # 登录接口
    location ^~ /api/ykz {
        proxy_pass http://userlogin;
    }

    # AI 流式接口(关闭 Buffer)
    location /chat/ {
        proxy_pass http://ai-backend:7889;
        proxy_buffering off;
        proxy_cache off;
        proxy_http_version 1.1;
        proxy_set_header Connection '';
        chunked_transfer_encoding on;
        gzip off;
        proxy_read_timeout 86400s;
        add_header X-Accel-Buffering "no" always;
    }

    # 监控(仅内网)
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 172.16.130.0/24;
        deny all;
    }
}

六、常见排坑指南

1. proxy_pass 末尾斜杠的坑

nginx 复制代码
# 请求 /api/user/login
location /api/ {
    proxy_pass http://backend/;       # → http://backend/user/login   (√ 期望行为)
    #proxy_pass http://backend;       # → http://backend/api/user/login (× 带了前缀)
    #proxy_pass http://backend/v2/;   # → http://backend/v2/user/login  (√ 换前缀)
}

记住proxy_pass 末尾有 / → 剥离 location 前缀;没有 / → 保留完整 URI。

2. 502 Bad Gateway 排查三板斧

bash 复制代码
# 1. 检查后端是否存活
curl -v http://172.16.130.43:8090/api/health

# 2. 检查 Nginx 错误日志
tail -f /data/logs/error.log | grep "upstream"

# 3. 看日志里的 upstream_addr 和 upstream_status
# 如果 upstream_addr 不对,说明 location 匹配到了错误的 upstream
tail -100 /data/logs/access.log | awk '{print $NF}'

日志里 $upstream_addr$upstream_status 就是这个用途------502 时直接 grep 日志,一秒定位是哪个后端挂了。

3. WebSocket 连接秒断

症状:WebSocket 连接建立后立即断开,客户端报 1006。

根因:漏配了这三行:

nginx 复制代码
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

配置里的 /api/bigscreen/api/app/api 都配了这三行------说明有 WebSocket 场景,是吃过这个坑的。

4. SSE 流式输出阻塞

症状:AI 对话等了很久才一次性吐出所有文字。

根因:没关 Buffer。Nginx 默认会缓存后端响应,等全部收完再发给客户端。

解法:按 5章第4 节的策略 B 来------proxy_buffering off 是核心,但必须配合 proxy_cache offgzip offchunked_transfer_encoding on

5. 上游 keepalive 连接池耗尽

nginx 复制代码
upstream wg {
    server 172.16.130.43:8090;
    keepalive 32;   # 保持 32 个到每个后端的空闲连接
}

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

如果不配 keepalive,Nginx 每次请求都要建新的 TCP 连接------高并发下就是大量的 TIME_WAIT。

6. rewrite 的 if 地狱

配置里有一个反例值得警惕:

nginx 复制代码
if ($yitu_proxy != "") {
    rewrite ^/(.*)$ /__yitu_proxy__/$1 last;
}

if 在 location 外使用是 当前配置唯一合法的场景 (基于 $变量 判断)。但如果写成这样就是坑:

nginx 复制代码
# 不推荐:if 嵌套 location
location / {
    if ($args ~ "debug=1") {
        proxy_pass http://debug-backend;  # 可能会被忽略
    }
    proxy_pass http://normal-backend;
}

在 location 内用 ifproxy_pass 分支是 Nginx 头号坑,行为不可预测。正确做法是用 map

nginx 复制代码
# 定义 map 匹配来自23.210.255.100得接口
map $http_x_real_ip $yitu_proxy {
    default             "";
    "23.210.255.100"    "172.16.130.73:18082";
} 
server {
  # 翼图 IP 过来的请求,先改写 URI 然后换到 internal location 处理
  if ($yitu_proxy != "") {
     rewrite ^/(.*)$ /__yitu_proxy__/$1 last;
  }

  # internal location:外部无法直接访问,只有 rewrite 进来的才会走到这里
  location /__yitu_proxy__/ {
     internal;
     proxy_pass http://$yitu_proxy;
     rewrite "^/__yitu_proxy__/(.*)$" /$1 break;
  }
}

这恰好是配置里 map $http_x_real_ip $yitu_proxy 的用法------根据请求头来源 IP 动态选择后端,优雅且可靠。


七、总结 + 落地清单

反向代理从"能跑"到"生产可用",中间差的不只是一个 proxy_pass

层次 要做什么 你配置里的解法
基础 proxy_pass + IP 透传 X-Real-IP + X-Forwarded-For
路由 location 匹配 + rewrite 剥离 ^~ 优先 + break 剥离前缀
高可用 负载均衡 + 健康检查 max_fails + fail_timeout 分敏感度
保护 限流 + 超时 limit_req_zone 多梯级 + 按接口设超时
性能 Buffer 调优 + Gzip + keepalive 大 Buffer / 零 Buffer 两套策略
流式 SSE / WebSocket 代理 proxy_buffering off + HTTP/1.1
监控 日志 + stub_status $upstream_response_time + $upstream_addr
安全 server_tokens off + 接口下线 return 404 / return 401 标记废弃
技巧 map 动态路由 + internal location 条件代理隔离不同来源

落地清单(逐条对照已有配置):

  • worker_cpu_affinity auto --- CPU 亲和性
  • use epoll + multi_accept on --- 事件模型优化
  • sendfile + tcp_nopush + tcp_nodelay --- 网络优化三件套
  • log_format$upstream_response_time --- 后端耗时监控
  • max_fails 分梯度 --- 健康检查分敏感度
  • proxy_buffering off 用于 AI/SSE --- 流式场景正确配置
  • limit_req_zone 多梯级 --- 接口限流分级
  • return 404 标记废弃接口 --- 接口生命周期管理
  • stub_status 内网监控 --- 运行状态可视化
  • keepalive 连接池 --- 建议在 wg upstream 里加上
  • userloginip_hash --- 防止登录态漂移

本文所有生产案例均来自真实运行的 Nginx 配置(18081 端口,日均千万级请求)。配置会过时,思路不会。