LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

Nginx 反向代理与负载均衡入门:30 分钟把后端网站服务器从「一台」变成「一组」

admin
2026年9月3日 9:39 本文热度 157

Nginx 可以充当静态文件服务器跑起来,但真实工作里 Nginx 90% 的用处是当反向代理——前面承接客户端流量,后面把请求转给一组 Node / Java / Python 后端。

这一篇按真实部署流程走:最小反代 → upstream 多后端 → 负载均衡策略 → 健康检查 → 长连接 → 真实 IP 透传。结尾给一份完整可粘贴的「Nginx 前 + 两个 Node 后端」demo,照着粘贴就能跑。


 一、正向 vs 反向代理一句话区别 

   
正向代理:代表客户端          反向代理:代表服务端
   ┌────┐    ┌──────┐               ┌──────┐    ┌────┐
   │Cli │ →  │Proxy │ → Internet → │Nginx │ →  │APP │
   └────┘    └──────┘               └──────┘    └────┘
   "翻墙、公司出口代理"            "Nginx、CDN、API 网关"

  • 正向代理
    :客户端知道 proxy 的存在、配置走代理;服务端不知道真实客户端是谁
  • 反向代理
    :客户端不知道 proxy 的存在、以为直接连服务端;服务端不知道真实客户端是谁(要靠 X-Forwarded-For 头透传)

Nginx、CDN、SLB、API 网关本质都是反向代理。本篇所有内容都围绕反向代理展开。


 二、最小反代示例:proxy_pass 一行就够 

假设后端是一个 Node 服务,监听在 127.0.0.1:3000

   nginx
# /etc/nginx/conf.d/my-api.conf
server {
    listen 80;
    server_name api.example.local;

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

nginx -t && nginx -s reload,访问 http://api.example.local/users 就被转发到后端。就这么简单一行

但生产可用配置不止这一行——还需要传 header、配超时、连接池、健康检查、负载均衡,下面一步步加。


 三、proxy_pass 的两种写法(踩坑王) 

proxy_pass 有两种写法,转发后的 URL 完全不同,这是新人最容易踩的坑:

写法 1:proxy_pass 不带 URI(保留原 URI)

   nginx
location /api/ {
    proxy_pass http://backend;            # 不带 /api 或别的 URI
}

请求 /api/users/123 → 后端收到 /api/users/123(原样转)

写法 2:proxy_pass 带 URI(路径替换)

   nginx
location /api/ {
    proxy_pass http://backend/;           # 带了一个 /
}

请求 /api/users/123 → 后端收到 /users/123location 前缀被替换成 proxy_pass 的 URI

用一张表对比

请求 URL
proxy_pass 配置
后端实际收到
/api/users/1proxy_pass http://backend;/api/users/1
/api/users/1proxy_pass http://backend/;/users/1
/api/users/1proxy_pass http://backend/v2;/v2users/1
 ❌(路径拼坏)
/api/users/1proxy_pass http://backend/v2/;/v2/users/1
 ✅

铁律

proxy_pass 后面带不带斜杠 /,转发后的 URL 完全不同

实战记忆法:

  • 想原样转:proxy_pass http://backend;不带任何路径
  • 想剥掉 location 前缀:proxy_pass http://backend/;带一个 /

还有一个高频翻车点:变量 + URI

   nginx
# ❌ 当 proxy_pass 里出现变量时,nginx 不会自动替换路径
location /api/ {
    set $backend "http://upstream-srv";
    proxy_pass $backend;
    # 此时无论加不加 / 都按"不带 URI"处理(原样转)
}

# ✅ 想剥前缀要自己 rewrite
location /api/ {
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass http://backend;
}


 四、必传 header 三件套:后端拿到真实 IP / Host 

默认情况下,请求被反代后后端只能看到 Nginx 的 IP,看不到真实客户端 IP;Host 头也会变成 upstream 的地址。要让后端拿到真实信息,得显式传 header。

   nginx
location / {
    proxy_pass http://backend;

    # 三件套
    proxy_set_header Host              $host;                    # 原始 Host
    proxy_set_header X-Real-IP         $remote_addr;             # 客户端真实 IP
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;  # 链式追加
    proxy_set_header X-Forwarded-Proto $scheme;                  # http / https
}

每一行的作用:

作用
Host$host
让后端知道访问的原始域名(多租户必备)
X-Real-IP$remote_addr
当前一跳的客户端 IP
X-Forwarded-For$proxy_add_x_forwarded_for
多级代理时追加(链式)
X-Forwarded-Proto$scheme
客户端访问的协议(用于后端生成正确的 redirect)

新人最容易忽略 Host 头:不传的话后端的 Host 头变成 backend多租户应用直接懵——不知道用户访问的是 a.example.com 还是 b.example.com。

X-Forwarded-For 的链式逻辑

   
客户端 1.2.3.4 → Nginx-A → Nginx-B → 应用

应用收到的 X-Forwarded-For = "1.2.3.4, 10.0.0.1"
                                ↑          ↑
                                客户端     Nginx-A
(X-Real-IP 通常只是当前 hop 的客户端,X-Forwarded-For 是完整链)

安全提醒不要盲信 X-Forwarded-For——客户端可以伪造任意值。只在自己信任的 reverse proxy 后面才能解析它,并且只取最右侧的可信 IP(你信任的代理设置的部分)。

抽成 proxy_params 复用

每个 location 都写一堆 proxy_set_header 太啰嗦,可以抽到一个文件:

   bash
sudo tee /etc/nginx/proxy_params <<'EOF'
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 5s;
proxy_send_timeout    60s;
proxy_read_timeout    60s;
EOF

后续 location 一行 include 即可:

   nginx
location / {
    include proxy_params;
    proxy_pass http://backend;
}

Debian / Ubuntu 包自带一份 proxy_params,可以直接 include 用。


 五、upstream 模块:定义后端组 

把后端从「一台」变「一组」就靠 upstream 块:

   nginx
# 在 http 上下文定义
upstream backend {
    server 10.0.0.10:3000;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
}

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

    location / {
        include proxy_params;
        proxy_pass http://backend;     # 注意 upstream 名要和这里对应
    }
}

upstream 内 server 指令的几个常用参数

   nginx
upstream backend {
    server 10.0.0.10:3000 weight=3;              # 权重(默认 1)
    server 10.0.0.11:3000 weight=1;              # 收到的流量是上面的 1/3
    server 10.0.0.12:3000 backup;                # 备用(其他都挂时才用)
    server 10.0.0.13:3000 down;                  # 标记下线(不参与)
    server 10.0.0.14:3000 max_fails=3 fail_timeout=30s;  # 失败 3 次拉黑 30s
    server 10.0.0.15:3000 max_conns=100;         # 该 server 最多并发 100
}


 六、负载均衡策略对比 

Nginx OSS 自带的策略:

策略
写法
适用场景
注意点
轮询(默认)
不写
无状态请求
完全均匀
加权轮询weight=N
后端配置不均(机器规格不同)
按权重比例
least_connleast_conn;
请求耗时差异大(长连接)
选当前连接数最少的
ip_haship_hash;
需要按客户端 IP 黏在固定后端
NAT 场景会塌缩
hash $keyhash $cookie_uid;
按任意键黏(如 cookie)
OSS 1.7.2+ 支持
random tworandom two least_conn;
大集群避免羊群效应
OSS 1.15.1+
   nginx
# 例 1:默认轮询
upstream backend {
    server 10.0.0.10:3000;
    server 10.0.0.11:3000;
}

# 例 2:least_conn
upstream backend {
    least_conn;
    server 10.0.0.10:3000;
    server 10.0.0.11:3000;
}

# 例 3:按 cookie 中的 uid 哈希
upstream backend {
    hash $cookie_uid consistent;       # consistent 是一致性 hash,扩缩容不会全部重洗
    server 10.0.0.10:3000;
    server 10.0.0.11:3000;
}

ip_hash 的塌缩问题

ip_hash 是新人最爱写、坑最多的策略

   nginx
upstream backend {
    ip_hash;       # 同 IP 总打到同一后端,看似能保会话
    server 10.0.0.10:3000;
    server 10.0.0.11:3000;
}

实际坑

  • 客户端经过 NAT(公司出口、家庭路由)时,几百个用户共享一个出口 IP,全部塌缩到同一后端,负载严重不均
  • 客户端走移动 4G/5G 网络,IP 频繁变动,"黏住"形同虚设
  • IP 变了用户被打到新后端,会话信息全丢

生产推荐

  • 完全无状态(推荐):用默认轮询或 least_conn
  • 需要会话保持:把会话信息放 Redis / Memcached 等集中存储,应用本身无状态
  • 实在要黏:用 hash $cookie_sessionid consistent 而不是 ip_hash

 七、健康检查:被动 vs 主动 

Nginx OSS 自带的是「被动健康检查」:等转发失败几次后摘掉。Nginx Plus 才有「主动健康检查」(定时去探)。

被动健康检查(OSS 自带)

   nginx
upstream backend {
    server 10.0.0.10:3000 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:3000 max_fails=3 fail_timeout=30s;
}

含义:

  • fail_timeout 内
    (30 秒),如果失败 ≥ max_fails 次(3 次),就把这个 server 拉黑
  • 拉黑期就是 fail_timeout(30 秒),到期后会再尝试
  • 「失败」的定义看 proxy_next_upstream 配置:默认包括 error timeout

两个高频问题

  1. 流量小的应用拉黑可能很慢
    :QPS 只有 1,3 次失败要 3 秒;如果间隔超过 fail_timeout,永远拉不黑
  2. 拉黑期到了试探时,那一个请求注定失败
    ——因为不知道恢复没

主动健康检查的替代方案

OSS 没有原生主动健康检查,常见三种替代:

  1. upstream_check_module
    (Tengine / 淘宝版第三方模块):定时探测
  2. OpenResty + lua-resty-upstream-healthcheck
    :Lua 实现
  3. 第三方框架
    :Consul / Nacos + nginx-template 动态更新 upstream

中型项目建议直接上 APISIX / Kong,主动健康检查、动态路由、监控一揽子。


 八、长连接:upstream keepalive 必须配 

默认情况下,Nginx 每次转发请求都会新建一条到后端的 TCP 连接,转发完关闭——3 次握手 + 4 次挥手,开销巨大。生产必须配 keepalive 复用

   nginx
upstream backend {
    server 10.0.0.10:3000;
    server 10.0.0.11:3000;

    keepalive 32;                              # 每个 worker 维护 32 个空闲连接
    keepalive_timeout 60s;                     # 空闲超过 60s 关
    keepalive_requests 1000;                   # 单连接处理 1000 个请求后关
}

server {
    location / {
        include proxy_params;
        proxy_pass http://backend;

        # **这两行是 keepalive 必须的,少一行就废**
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

为什么要 proxy_http_version 1.1 和 Connection ""

  • HTTP/1.0 默认不支持长连接,必须 1.1
  • 默认 Connection: close 会强制关连接,要置空(让 nginx 自己加 keep-alive)

漏配的代价:高 QPS 场景下 nginx 与后端之间 TIME_WAIT 暴涨,源端口耗尽,新连接全部失败。


 九、完整 demo:Nginx 前 + 两个 Node 后端 

来一份完整可粘贴、上手即跑的最小 demo。

后端:起两个 Node 服务

   js
// app.js
const http = require('http');
const port = process.env.PORT || 3000;

http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type''application/json' });
    res.end(JSON.stringify({
        from`node-${port}`,
        path: req.url,
        host: req.headers.host,
        realIp: req.headers['x-real-ip'],
        forwardedFor: req.headers['x-forwarded-for'],
    }));
}).listen(port);

console.log(`Backend listening on ${port}`);
   bash
# 一个终端
PORT=3001 node app.js

# 另一个终端
PORT=3002 node app.js

Nginx 配置

   nginx
# /etc/nginx/conf.d/demo.conf

upstream backend {
    least_conn;

    server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3002 max_fails=3 fail_timeout=10s;

    keepalive 16;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

server {
    listen 80;
    server_name demo.local;

    access_log /var/log/nginx/demo.access.log;
    error_log  /var/log/nginx/demo.error.log warn;

    # 健康检查端点(让自己监控用)
    location = /healthz {
        return 200 "ok\n";
        access_log off;
    }

    location / {
        proxy_pass http://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;

        # 长连接
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # 超时
        proxy_connect_timeout 5s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;

        # 失败重试(默认 nginx 会自动 next_upstream,这里显式声明)
        # ⚠️ 默认只对 GET / HEAD 等幂等请求 retry;如果允许 POST 重试,
        # 需要 proxy_next_upstream_methods,但生产慎用——会重复写
        proxy_next_upstream error timeout http_502 http_503;
        proxy_next_upstream_tries 2;
        proxy_next_upstream_timeout 10s;
    }
}

验证

   bash
# reload
sudo nginx -t && sudo nginx -s reload

# 加 hosts
echo "127.0.0.1 demo.local" | sudo tee -a /etc/hosts

# 连续访问,看是不是轮到不同后端
for i in {1..6}; do
    curl -s http://demo.local/api/users | jq '.from'
done
# "node-3001"
# "node-3002"
# "node-3001"
# "node-3002"
# ...

# 看 Host 头透传是不是正确
curl -s http://demo.local/test | jq
# {
#   "from": "node-3001",
#   "path": "/test",
#   "host": "demo.local",          ← Host 头正确
#   "realIp": "127.0.0.1",         ← X-Real-IP 正确
#   "forwardedFor": "127.0.0.1"
# }

# 模拟一个后端挂掉
# Ctrl+C 杀掉 3001
# 再连续访问,看 nginx 是否自动避开
for i in {1..6}; do curl -s http://demo.local/ | jq '.from'done
# "node-3002"
# "node-3002"
# ...

# 测完清理 hosts,避免污染本机解析
sudo sed -i '/demo.local/d' /etc/hosts


 十、常见踩坑清单 

现象
原因
解法
后端拿不到真实 IP
没配 X-Real-IP / X-Forwarded-For
加 proxy_set_header 三件套
后端 Host 是 backend
没传 Host 头
proxy_set_header Host $host;
nginx 与后端 TIME_WAIT 爆炸
没配 keepalive
upstream 加 keepalive + location 加 http_version 1.1
502 Bad Gateway
后端挂了 / 连接被拒
看 error.log 的 connect() failed
504 Gateway Timeout
后端响应慢 / 超时太短
调 proxy_read_timeout
503 No upstreams
upstream 里 server 全被拉黑
检查后端是否健康,调 max_fails
域名后端解析后变化无感
OSS 启动时只解析一次 DNS
用 resolver 8.8.8.8 valid=30s; + 变量绕过
ip_hash 流量严重不均
NAT 出口 IP 塌缩
换 hash $cookie_xxx 或彻底无状态
反代后 POST 请求 body 丢
用了 return / rewrite ... last;
用 proxy_pass 直接转发,不要中间 rewrite 改 method

DNS 解析的隐藏坑

   nginx
# ❌ OSS 中 server 域名只在启动 / reload 时解析一次
upstream backend {
    server api.internal.example.com:3000;
}
# 后端 IP 变了 nginx 完全无感,一直打到旧 IP(除非 reload)

# ✅ 用 resolver + 变量绕过
http {
    resolver 8.8.8.8 valid=30s ipv6=off;
}

server {
    location / {
        set $upstream "api.internal.example.com";
        proxy_pass http://$upstream:3000;
    }
}
# 用变量后,每次请求都按 resolver 的 valid 时间重新解析

K8s 场景下后端 Pod IP 频繁变化,这个坑特别致命。生产建议用 Service ClusterIP(稳定)或上 APISIX / Envoy(原生支持服务发现)。


 写在最后 

  • proxy_pass 带 / 与不带 / 转发路径完全不同
    ,新人栽得最频繁的一个点
  • header 三件套必传
    :Host / X-Real-IP / X-Forwarded-For,否则后端拿不到真实信息
  • upstream 必配 keepalive + location 必配 http_version 1.1 + Connection “”
    ,少一行 TIME_WAIT 爆
  • ip_hash 在 NAT 场景会塌缩
    ,需要会话保持优先考虑无状态 + Redis
  • OSS 的健康检查是被动
    的,需要主动健康检查的上 OpenResty 模块或换 APISIX / Kong
  • upstream server 写域名要小心
     DNS 缓存问题,要么用 IP,要么 resolver + 变量绕过
  • 排查反代问题先看 error.log:connect refused = 后端没起 / timeout = 网络或后端慢 / 502 = 后端异常退出


阅读原文:点击这里


该文章在 2026/9/3 9:41:09 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号