🚀 第03章 · 反向代理

proxy_pass 原理解析

反向代理

前一章的 location / { proxy_pass ... } 就是反向代理。它是 Nginx 最核心的用途:把 Nginx 收到的请求"转交"给后端的某个应用服务处理,再把结果拿回来递给客户端。理解 proxy_pass 及配套的请求头转发,你就能真正看懂本机所有站点的配置文件。

正向与反向代理的区别

  • 正向代理站在客户端一侧,替客户端去访问互联网(如翻墙、公司上网代理)。客户端知道代理的存在。
  • 反向代理站在服务器一侧,客户端只看到 Nginx,不知道后面藏着多少个应用。客户端以为自己在直接访问"网站"。

本机就是标准的反向代理拓扑:

客户端 ──HTTPS──> Nginx (外层,终止TLS) ──HTTP──> 127.0.0.1:3000 (Bang Dream 后端)
                                          ──> 127.0.0.1:3010 (Love Live 后端)
                                          ──> 127.0.0.1:3081 (DSH web UI)
                                          ──> 127.0.0.1:8090 (GitLab)

理解 换成反向代理的一大好处是隐藏后端:后端应用不用暴露公网端口,只监听 127.0.0.1,外部流量全部经 Nginx 转发。TLS 证书也只需在外层 Nginx 统一管理一个地方。

proxy_pass 的原理

proxy_pass 把当前 location 匹配到的请求转发到指定后端,并默认透传请求体、返回响应体。基础写法:

server {
    listen 443 ssl;
    server_name bangdream.xyz www.bangdream.xyz;

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

任何访问 https://bangdream.xyz/xxx 的请求,都会被 Nginx 转发到 http://127.0.0.1:3000/xxx。

易错 这是反向代理里最容易踩的坑:proxy_pass 末尾有没有斜杠,规则完全不同。

  • proxy_pass http://127.0.0.1:3000;(无斜杠)——把整个原 URI 透传。请求 /api/user → 后端 /api/user。
  • proxy_pass http://127.0.0.1:3000/;(有斜杠)——把 location 匹配到的前缀替换掉。若 location 是 /api,请求 /api/user → 后端 /user。

本机四个站点用的都是无斜杠写法,URI 原样透传,这是最常见的需求。

location 与 proxy_pass 的路径拼接

配合 location 前缀,proxy_pass 的路径拼接需要格外小心。假设有:

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

location /api/ {
    proxy_pass http://127.0.0.1:3000;   # 注意:没有斜杠
}
请求 无斜杠时后端收到 有斜杠时后端收到
/api/user /api/user /user
/api/ /api/ /

规则一句话:带斜杠 proxy_pass 会"削掉" location 前缀;不带斜杠则原样拼接。记不住就独立测试,别靠猜。

转发后端需要的请求头

反向代理默认会把请求转给后端,但有几个头属于"代理场景专用",不会自动加,必须手动 proxy_set_header。本机每个 location 都写了同一组:

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;
指令 作用
Host $host 把客户端请求的原始域名交给后端(后端据此做路由/校验 host)
X-Real-IP $remote_addr 告诉后端"真正的客户端 IP"
X-Forwarded-For 加上这一跳的客户端 IP(用 $proxy_add_x_forwarded_for 会保留原有的链)
X-Forwarded-Proto $scheme 告诉后端"客户端原本是 http 还是 https"。后端生成链接时用它拼对协议

易错 不加 Host $host 时,Nginx 默认传给后端的是 proxy_pass 里那个地址,后端很可能会因为 host 不匹配而拒绝、或生成错误的跳转链接。凡是要反代的 location,Host $host 几乎必加。

WebSocket 反向代理

WebSocket(长连接、双向通信)的握手依赖 Upgrade 请求头,而 HTTP/1.0 的默认代理行为不支持。所以必须做两件事:升级到 HTTP/1.1,并透传 Upgrade/Connection。看本机 dsh.bangdream.xyz:

location / {
    proxy_pass http://127.0.0.1:3081;
    proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header 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;
}

而 GitLab 的配置里 Connection 用的是变量,配合主配置里的 map:

# nginx.conf: http 块内
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# gitlab 的 location / 里
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

两种写法的差别:字面量 "upgrade" 会无条件把 Connection 设为 upgrade,不管请求是不是 WebSocket;而 $connection_upgrade 则聪明地判断——Upgrade 头非空才传 upgrade,否则传 close。

易错 proxy_http_version 1.1 必须配。默认 proxy_http_version 1.0 的 Connection: close,WebSocket 的 Upgrade 握手会直接失败,表现为连接后瞬间断开。

超时设置

长连接服务(WebSocket、SSE、GitLab 的在线编辑)需要宽松的超时,否则 Nginx 会提前断开。用到的三类超时:

指令 作用 本机值
proxy_read_timeout 后端两次读取之间的最大等待时间 3600s(dsh)/ 300s(gitlab)
proxy_send_timeout 向前端发送数据两次之间的等待 3600s
proxy_connect_timeout 连上后端的超时(默认 60s) 未设,用默认

理解 这些超时的语义是"同一次读/写之间停滞多长时间",不是"整个请求总时长"。所以 proxy_read_timeout 3600s 允许 WebSocket 空闲一小时不断开,而不是限制每次请求一分钟。

上传大小限制

默认 Nginx 限制请求体最大为 1MB,推送代码或上传视频会报 413。GitLab 因为要接收大文件推送,专门放宽:

client_max_body_size 0;   # 0 表示不限制

易错 遇到 413 Request Entity Too Large 基本就是它。按需改成 client_max_body_size 100m; 这样的具体值,或 0 不限制。它要写在 server 或 location 层。

阻止敏感路径被代理

反向代理的一个风险是:如果不小心把后端的源码目录、脚本、依赖清单暴露出去,等于把应用内部摊在公网。本机用 location 正则拦截:

location ~ ^/(node_modules|scripts)/ { return 403; }
location = /package.json { return 403; }
location = /package-lock.json { return 403; }

安全 这组规则把后端的 node_modules(依赖包)、scripts/(构建脚本)、package.json(依赖清单)全部挡在代理层,直接返回 403,根本不转发到后端。等于是"防火墙前置",比让后端自己防护更彻底。具体匹配原理下一章会展开。

小结

反向代理让 Nginx 成为站点流量"总入口":用 proxy_pass 把请求交给后端;用 proxy_set_header 补上 Host、真实 IP、协议;WebSocket 场景升级到 HTTP/1.1 并透传 Upgrade;长连接调大超时;上传场景放开 client_max_body_size;再用 location 正则把敏感路径拦在代理层外。下一章,我们把 proxy_pass 的目标从"单台后端"升级成"一组后端"——负载均衡。