Chapter 05

反向代理

把请求转发给后端应用,是 Nginx 在现代架构中最核心的角色。proxy_pass 只是一行,但请求头传递、超时、缓冲、WebSocket 才是能不能用对的关键。

5.1 核心概念

proxy_pass
把匹配到的请求转发到指定的后端地址(应用服务器或 upstream 组)。是反向代理的核心指令。
upstream
定义一组后端服务器,供 proxy_pass 引用。可配置负载均衡策略、权重、健康检查(第 6 章详解)。
X-Forwarded-For(XFF)
代理链上追加真实客户端 IP 的请求头。经过多层代理时,后端靠它拿到原始 IP 而非代理 IP。
X-Real-IP
只携带直连客户端 IP 的头,比 XFF 简单,后端应用常用它记日志、做限流。
proxy_buffering
Nginx 是否先把后端响应缓冲到内存/磁盘再发给客户端。开启利于慢客户端场景;流式响应(SSE)需关闭。
WebSocket 升级(Upgrade)
WebSocket 握手时靠 UpgradeConnection 头把 HTTP 连接升级为长连接。代理时必须显式转发这两个头,否则握手失败。

5.2 最小反向代理

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

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

请求打到 Nginx 的 80 端口,被原样转发到本机 3000 端口跑的 Node/Java 应用。客户端全程不知道后端在哪、有几台。但上面这份配置不完整——后端拿不到真实客户端信息。

5.3 正确传递请求头

默认转发时,后端看到的 Host 是空或代理地址,客户端 IP 变成了 Nginx 的 IP。必须手动补齐这些头:

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

    # 把原始 Host 传给后端(虚拟主机、重定向需要)
    proxy_set_header  Host  $host;
    # 真实客户端 IP
    proxy_set_header  X-Real-IP  $remote_addr;
    # 代理链 IP 列表
    proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
    # 原始协议(后端据此判断是否 HTTPS)
    proxy_set_header  X-Forwarded-Proto  $scheme;
}
把这四行做成 snippet 复用

这组 proxy_set_header 几乎每个反代 location 都要写。把它们存进 /etc/nginx/snippets/proxy-headers.conf,各处 include 引入,既统一又不易漏。后端框架(Express/Spring)也要配置"信任代理"才会读这些头。

5.4 proxy_pass 的 URI 处理陷阱

proxy_pass 后面带不带路径,行为大不同:

不带路径(保留 URI)

location /api/ {
  proxy_pass http://backend;
}

/api/users/api/users

带路径(替换前缀)

location /api/ {
  proxy_pass http://backend/;
}

/api/users/users

规则:proxy_pass 的地址带了 URI 部分(哪怕只是一个 /,location 匹配的前缀就会被替换掉;不带 URI 则原样透传。这决定后端收到的路径,务必测清楚。

5.5 用 upstream 组织后端

http {
    upstream app_backend {
        server  127.0.0.1:3000;
        server  127.0.0.1:3001;
        keepalive  32;        # 与后端保持长连接,减少握手开销
    }

    server {
        listen 80;
        location / {
            proxy_pass  http://app_backend;
            proxy_http_version  1.1;          # keepalive 需要 1.1
            proxy_set_header  Connection  "";  # 清空以复用连接
            proxy_set_header  Host  $host;
        }
    }
}

5.6 超时与缓冲

location / {
    proxy_pass  http://app_backend;

    # 三类超时
    proxy_connect_timeout  5s;    # 与后端建连超时
    proxy_send_timeout     60s;   # 向后端发请求超时
    proxy_read_timeout     60s;   # 等后端响应超时

    # 缓冲后端响应
    proxy_buffering  on;
    proxy_buffers  8 16k;
    proxy_buffer_size  16k;
}
流式响应要关闭 buffering

Server-Sent Events(SSE)、大文件流式下载、LLM 逐字返回等场景,若开着 proxy_buffering on,Nginx 会攒够一批才发,用户感觉"卡住"。这些 location 要 proxy_buffering off;,让数据即产即发。

5.7 代理 WebSocket

WebSocket 需要把 HTTP 连接"升级"为持久连接,必须显式转发 Upgrade/Connection 头,并用 HTTP/1.1:

location /ws/ {
    proxy_pass  http://app_backend;
    proxy_http_version  1.1;
    proxy_set_header  Upgrade  $http_upgrade;
    proxy_set_header  Connection  "upgrade";
    proxy_set_header  Host  $host;

    # WebSocket 是长连接,读超时要放大
    proxy_read_timeout  3600s;
}
为什么 Connection 要设成 "upgrade"

HTTP/1.1 里 Connection 是逐跳(hop-by-hop)头,Nginx 默认不透传。WebSocket 握手要求 Connection: upgrade,所以必须显式 proxy_set_header Connection "upgrade";,否则后端收不到升级请求,握手失败返回 400。

5.8 小结与下一步