Skip to content

Nginx 反向代理

反向代理位于客户端和后端应用之间——客户端访问 Nginx,Nginx 再把请求转发给真实服务:

text
client → nginx → backend

在运维场景中,Nginx 作为入口代理通常承担:统一入口(对外只暴露 Nginx 地址)、负载均衡(把请求分发到多个后端)、TLS 终止(证书放在 Nginx 上)、静态资源直返、访问日志记录和基础的限流超时保护。

Nginx 适合做入口代理(TLS 终止、域名路由、静态资源、基础限流、反向代理),但它不是 API 网关——复杂鉴权、租户策略、细粒度路由、服务治理这类能力如果全部写进 Nginx 配置,规则会越来越绕,发布和排查都会变重。小环境可以直接用 Nginx 扛入口,大一些的系统通常还会配专门的网关或应用层治理组件。

一、最小代理配置

后端应用监听 127.0.0.1:8080

nginx
server {
    listen 80;
    server_name app.example.local;

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

验证:

bash
nginx -t && systemctl reload nginx
curl -H 'Host: app.example.local' http://127.0.0.1/

二、请求头传递

代理时保留原始 Host 和客户端 IP 很关键——后端应用依赖这些信息生成回调地址、记录审计日志、判断来源 IP:

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

    proxy_set_header Host $host;                   # 保留用户访问的域名
    proxy_set_header X-Real-IP $remote_addr;        # 直接连接 Nginx 的客户端 IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;     # 标记原始请求是 http 还是 https
}

各请求头的作用:

请求头做什么
Host用户浏览器里输入的域名,后端据此生成正确的跳转 URL
X-Real-IP直接连到 Nginx 的客户端 IP
X-Forwarded-For完整的代理链路 IP,$proxy_add_x_forwarded_for 是在已有的基础上追加
X-Forwarded-Proto原始请求协议,后端据此生成 https:// 而非 http:// 的回调地址

多层代理时 X-Forwarded-For 会是一串 IP。后端取"真实客户端 IP"时,要配置可信代理 IP 范围——不能直接信任 X-Forwarded-For 最左边那个值,因为客户端可以伪造这个头。

三、proxy_pass 路径规则——斜杠的差别

proxy_pass 末尾有没有 / 会改变转发的 URI:

nginx
# 情况一:proxy_pass 末尾无斜杠
location /api/ {
    proxy_pass http://127.0.0.1:8080;
}
# /api/users → 转发到 http://127.0.0.1:8080/api/users(location 前缀保留)

# 情况二:proxy_pass 末尾有斜杠
location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}
# /api/users → 转发到 http://127.0.0.1:8080/users(location 前缀被替换)

线上最常见的代理 404 原因之一就是斜杠差异——本地调试时路径碰巧对得上,上线后后端路由前缀不一样就 404。排查时把 Nginx 配置里的 proxy_pass 和后端实际路由前缀放在一起对着看。

四、upstream 负载均衡

多个后端写成一个 upstream 组:

nginx
upstream app_backend {
    server 192.168.10.21:8080;
    server 192.168.10.22:8080;
}

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

    location / {
        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;
    }
}

默认是轮询(round-robin),其他分发方式:

方式配置适用场景
轮询默认,不写就是后端规格一致
权重server ... weight=2后端机器配置不同
最少连接least_conn;请求耗时差异大(长短连接混合)
IP haship_hash;简单的会话保持

权重示例:

nginx
upstream app_backend {
    server 192.168.10.21:8080 weight=2;   # 这台配得高,分到更多请求
    server 192.168.10.22:8080 weight=1;
}

五、后端故障和重试

开源版 Nginx 的 upstream 健康检查是被动的——请求失败后才把后端临时标记为不可用:

nginx
upstream app_backend {
    server 192.168.10.21:8080 max_fails=3 fail_timeout=30s;
    server 192.168.10.22:8080 max_fails=3 fail_timeout=30s;
}
参数含义
max_fails=3fail_timeout 时间内失败 3 次后,该后端被临时摘除
fail_timeout=30s失败计数的统计窗口,也是摘除持续的时间

代理层重试:

nginx
location / {
    proxy_pass http://app_backend;
    proxy_next_upstream error timeout http_502 http_503 http_504;
    proxy_next_upstream_tries 2;  # 最多尝试 2 个后端,避免无限重试放大流量
}

重试只对幂等请求(GET)安全。下单、支付、创建任务这类请求如果在入口层被重试,后端可能收到两次写入——所以应用自身要有幂等保护,不能依赖 Nginx 层面不重试。

六、超时和缓冲

nginx
location / {
    proxy_pass http://app_backend;

    proxy_connect_timeout 3s;   # 连接后端的超时——连不上时快速失败
    proxy_send_timeout 30s;     # 向后端发送请求体的超时
    proxy_read_timeout 30s;     # 等待后端返回响应的超时——接口慢时先看这个
}

三个超时的分工:

参数超时发生在哪个阶段
proxy_connect_timeoutTCP 握手阶段——后端端口是否可达
proxy_send_timeout发送请求体阶段——Nginx 把请求数据发给后端
proxy_read_timeout等待响应阶段——后端多久没返回数据,最常见超时场景

缓冲配置:

nginx
location / {
    proxy_pass http://app_backend;
    proxy_buffering on;           # 开启缓冲:Nginx 收完后端响应再发给客户端
    proxy_buffers 16 16k;
    proxy_busy_buffers_size 64k;
}

缓冲对普通 HTTP 接口是好事——后端快慢都先由 Nginx 扛着,客户端不会直接感受到后端的抖动。但流式响应(SSE、文件下载、实时推送)需要关闭缓冲,否则客户端迟迟看不到数据:

nginx
location /events/ {
    proxy_pass http://app_backend;
    proxy_buffering off;         # 流式响应:后端吐一点 Nginx 就传一点
    proxy_read_timeout 1h;       # 长连接放宽超时
}

七、WebSocket

WebSocket 需要协议升级:

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;               # 没有 Upgrade 头时,Connection 设为 close
}

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

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

        proxy_http_version 1.1;                    # WebSocket 必须 HTTP/1.1
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 1h;                     # 长连接超时放宽
    }
}

WebSocket 断连时,除了看 Nginx 配置,还要排查链路上每一层:中间负载均衡的空闲超时、浏览器控制台的错误信息、后端应用的连接日志。很多断连不是单点问题,而是某一层空闲超时设得太短。

八、日志——区分 Nginx 耗时和后端耗时

默认日志不够排查代理问题,加一个自定义格式把 upstream 状态和耗时打出来:

nginx
log_format proxy_main '$remote_addr - $host "$request" '
                      'status=$status body=$body_bytes_sent '
                      'request_time=$request_time '
                      'upstream=$upstream_addr '
                      'upstream_status=$upstream_status '
                      'upstream_time=$upstream_response_time';

access_log /var/log/nginx/app.access.log proxy_main;

关键字段:

字段含义
$request_timeNginx 从收到请求到完成响应的总耗时(秒)
$upstream_addr实际转发到的后端地址
$upstream_status后端返回的 HTTP 状态码
$upstream_response_time后端处理耗时(秒)

接口慢时,这几个字段能区分是谁的问题:$request_time 高、$upstream_response_time 也高 → 后端慢;$request_time 高但 $upstream_response_time 正常 → 客户端传输慢或 Nginx 侧排队;$upstream_status 是 502/504 → 后端挂了或超时。

九、常见故障

现象常见原因排查方向
502 Bad Gateway后端端口不可达、进程挂了、协议不对ss -lntp 看端口、后端日志、Nginx error log
504 Gateway Timeout后端响应超过 proxy_read_timeout后端耗时、数据库查询、外部接口调用
404(代理后)proxy_pass 斜杠导致路由错位对比 Nginx 转发路径和后端路由
后端拿不到真实 IP请求头没传或后端没信任代理X-Forwarded-For、应用框架代理配置
只有一个后端有流量upstream 配置写了多台但只有一台被用到access 日志里的 $upstream_addr
大文件上传失败client_max_body_size 默认 1MB加大限制:client_max_body_size 100m;

排查入口:先看 access 日志里的状态码、$upstream_addr 和耗时,再看 error 日志。只看浏览器里一个 502,无法判断是 Nginx、网络还是后端的问题。