🚀 第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 的目标从"单台后端"升级成"一组后端"——负载均衡。