主题
05 · 反向代理 · Nginx 的灵魂技能
生活类比:反向代理就是酒店前台——你(用户)来住店,前台帮你联系不同部门:客房部、餐饮部、会议室。你只跟前台打交道,根本不知道后面有几个人在服务。
这一章是 Nginx 真正的"杀手锏"——前端 / 后端 / 运维都必须吃透。
1. 正向代理 vs 反向代理 · 一图分清
┌────────── 正向代理(代理客户端)──────────┐
│ │
│ 你 ──► 翻墙 VPN ──► Google │
│ (帮你绕墙) │
│ │
└──────────────────────────────────────────────┘
┌────────── 反向代理(代理服务器)──────────┐
│ │
│ 用户 ──► Nginx ──► 真正的后端服务 │
│ (隐藏后端) │
│ │
└──────────────────────────────────────────────┘| 维度 | 正向代理 | 反向代理 |
|---|---|---|
| 谁知道存在 | 客户端知道,服务端不知 | 服务端知道,客户端不知 |
| 配置在哪 | 客户端 | 服务端入口 |
| 典型例子 | VPN、公司出网代理 | Nginx、CDN、API 网关 |
一句话记忆:正向代客、反向代服。
2. 最简反向代理(30 秒上手)
场景:浏览器请求 /api/users → 转发到本机 Node 服务(端口 3000)
nginx
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}整个 Nginx 配置就这一行核心指令:proxy_pass。
⚠️ 但生产环境只写这一行远远不够——下面会讲完整套路。
3. proxy_pass 的"斜杠玄学"⚠️
这是 Nginx 第一个能让人秃头的小坑。
4 种写法 · 对比
假设请求是 GET /api/users/1:
| 配置 | 真正打到后端的 URL | 标记 |
|---|---|---|
location /api/ → proxy_pass http://b/ | http://b/users/1 | ✅ 截掉前缀 |
location /api/ → proxy_pass http://b | http://b/api/users/1 | ✅ 保留前缀 |
location /api → proxy_pass http://b/ | http://b//users/1(坑!) | ❌ 多个斜杠 |
location /api → proxy_pass http://b | http://b/api/users/1 | ✅ 保留前缀 |
口诀:
proxy_passURL 末尾有/→ Nginx 会把location前缀截掉再拼路径proxy_passURL 末尾没/→ Nginx 把完整 URL 透传过去
生活类比:
- 有
/:前台说"给二楼工程部",但只把"用户问题"转过去(改写过的需求单) - 没
/:前台说"给二楼工程部",把整张原始需求单直接转过去
4. 必加的 5 个 proxy_set_header
问题:经过 Nginx 的请求,后端拿到的 client IP 永远是 Nginx 自己!日志、风控、限流全废。
解决:转发时手动塞几个头,把"原始信息"传给后端:
nginx
location /api/ {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $host; # ① 原始域名
proxy_set_header X-Real-IP $remote_addr; # ② 原始客户端 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # ③ 链路 IP 列表
proxy_set_header X-Forwarded-Proto $scheme; # ④ http 还是 https
proxy_set_header X-Forwarded-Host $host; # ⑤ 原始 Host
}| Header | 作用 |
|---|---|
Host | 后端看到原始域名,否则会拿到 127.0.0.1:3000 |
X-Real-IP | 后端拿用户真实 IP(最简单) |
X-Forwarded-For | 经过的代理链 IP 列表(标准做法) |
X-Forwarded-Proto | 后端知道用户访问的是 http 还是 https |
X-Forwarded-Host | 同 Host,备用 |
后端读 IP 的伪代码(Node.js / Express):
js
const realIp =
req.headers['x-real-ip'] ||
(req.headers['x-forwarded-for'] || '').split(',')[0].trim() ||
req.socket.remoteAddress;⚠️ 安全注意:
X-Forwarded-For是请求头,用户可以伪造!只有"信任的代理"传过来的才能采信。详见 qa.md 第 14 题。
5. 超时控制 · 别让一个慢接口拖垮整个 Nginx
nginx
location /api/ {
proxy_pass http://backend/;
proxy_connect_timeout 5s; # 连后端最多等 5 秒
proxy_send_timeout 30s; # 发请求体到后端最多等 30 秒
proxy_read_timeout 30s; # 读后端响应最多等 30 秒
}| 指令 | 默认 | 推荐 |
|---|---|---|
proxy_connect_timeout | 60s | 5s(建连应该极快) |
proxy_send_timeout | 60s | 30s |
proxy_read_timeout | 60s | 30s(业务慢可放宽到 60s) |
长连接 / WebSocket 业务要单独大幅放宽
read_timeout,下面讲。
6. WebSocket 反向代理(必考)
WebSocket 是基于 HTTP 的"协议升级"——浏览器先 GET,请求头里带 Upgrade: websocket,让服务器把 TCP 连接"升级"成 WS。
nginx
location /ws/ {
proxy_pass http://127.0.0.1:4000/;
proxy_http_version 1.1; # ① 必须 HTTP/1.1
proxy_set_header Upgrade $http_upgrade; # ② 透传 Upgrade
proxy_set_header Connection "upgrade"; # ③ 改 Connection 为 upgrade
proxy_read_timeout 3600s; # ④ 长连接,把超时拉长
}少任何一个 → WebSocket 第一时间断开,新人 90% 的"WebSocket 连不上"问题都在这。
7. 给前端跨域的优雅解法 · 让 Nginx 兜跨域
前端访问 /api/users 时,浏览器认为这是"同源"(因为前端域名也是 app.example.com),不会触发跨域。Nginx 在背后偷偷转发到后端 → 完美解决跨域问题。
传统方案: 前端 (app.com) ─跨域─► 后端 (api.com)
└─ 后端要配 CORS
Nginx 反代方案: 前端 (app.com) ┌─► / → 静态文件
│ │
└──► Nginx ─────────┤
└─► /api/ → 转给 api.com(同源,无跨域)nginx
server {
server_name app.example.com;
location / {
root /var/www/myapp;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://api.example.com/; # 后端在另一个域,nginx 内部转发
# ... 标准 5 个 proxy_set_header ...
}
}前端请求时直接写 fetch('/api/users') —— 既无跨域、又不需要后端配 CORS。这是中小项目最常用的解法。
8. 透传上传请求(大文件 / multipart)
nginx
location /api/upload {
proxy_pass http://backend/upload;
client_max_body_size 100m; # 默认 1m,太小!
proxy_request_buffering off; # 别在 Nginx 这缓存整个文件再发,直接流式转发
}proxy_request_buffering off 的好处:上传 1GB 文件不会先把 1GB 攒在 nginx 内存里。
9. 实战模板:完整的 SPA + API + WS 配置
nginx
upstream api_backend {
server 127.0.0.1:3000;
keepalive 64; # 与后端保持长连接,提速
}
server {
listen 80;
server_name app.example.com;
root /var/www/myapp;
# 1. SPA
location / {
try_files $uri $uri/ /index.html;
}
# 2. API
location /api/ {
proxy_pass http://api_backend/;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 启用 keepalive
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_read_timeout 30s;
}
# 3. WebSocket
location /ws/ {
proxy_pass http://127.0.0.1:4000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
}10. ⚠️ 反向代理 7 大踩坑总结
proxy_pass末尾斜杠 → 见第 3 节"斜杠玄学"- 后端拿不到真实 IP → 没设
X-Real-IP/X-Forwarded-For - WebSocket 连不上 → 没配
Upgrade三件套 - upload 413 →
client_max_body_size没改 - 后端 Host 是
127.0.0.1:3000→ 没设proxy_set_header Host $host - 后端日志显示请求是 http,但用户用的 https → 没设
X-Forwarded-Proto X-Forwarded-For被用户伪造 → 入口的 Nginx 应该重置这个头:proxy_set_header X-Forwarded-For $remote_addr;
11. 章末面试题速览
详见
qa.md第 12-15 题。
- 正向代理 vs 反向代理? → 正向代客户端、反向代服务器。
proxy_pass末尾的/有什么影响? → 有/会截掉 location 前缀,没/透传完整 URL。- 怎么让后端拿到真实 IP? →
proxy_set_header X-Real-IP $remote_addr; - Nginx 代理 WebSocket 要注意啥? →
proxy_http_version 1.1+Upgrade/Connection "upgrade"+ 拉长read_timeout。
12. 一句话总结
反向代理 = 把请求转给后端:核心一行
proxy_pass、必加 5 个 header、记牢"末尾斜杠玄学"——这就是 Nginx 80% 的工作内容。
下一章 → 06 · 负载均衡:把请求平均分给多台后端。
🎬 可视化演示
下方 demo 模拟反向代理:你在浏览器请求 /api/...,看 Nginx 怎么把请求 + headers 重写后发给后端,再把响应一路传回浏览器。
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗