88 lines
4.7 KiB
Markdown
88 lines
4.7 KiB
Markdown
---
|
||
id: devops.nginx.proxy-static
|
||
title: 反向代理、负载均衡和静态资源:location 匹配、proxy_pass、SPA 回退
|
||
type: lesson
|
||
domain: devops
|
||
stack: [nginx]
|
||
tags: [反向代理, location, proxy_pass, upstream, try_files, gzip]
|
||
level: basic
|
||
est_hours: 2
|
||
related: [devops.nginx.https-stream-limit, devops.nginx.interview]
|
||
week: [3, 7]
|
||
order: 1
|
||
---
|
||
|
||
# 反向代理、负载均衡和静态资源:location 匹配、proxy_pass、SPA 回退
|
||
|
||
## 解释说明
|
||
|
||
- **正向代理 vs 反向代理**:正向代理代表客户端(翻墙代理),服务器不知道真实客户端;反向代理代表服务端,客户端只看到 Nginx,不知道后面有几台服务器。
|
||
- **配置结构**:`http` → `server`(按域名和端口区分站点)→ `location`(按路径匹配)。
|
||
- **location 匹配优先级**:`=` 精确匹配最高;然后找最长的前缀匹配,如果它带 `^~` 就直接用;否则按配置顺序检查正则(`~` 区分大小写、`~*` 不区分),第一个匹配的正则胜出;都不匹配就用最长前缀。
|
||
- **`proxy_pass` 末尾的斜杠**:`location /api/ { proxy_pass http://backend; }` 会把 `/api/users` 原样转发;`proxy_pass http://backend/;`(带路径)会把匹配的 `/api/` 替换成 `/`,后端收到 `/users`。
|
||
- **转发真实客户端信息**:后端看到的来源 IP 是 Nginx,要通过 `X-Real-IP`、`X-Forwarded-For`、`X-Forwarded-Proto` 传递;后端框架要配置信任这个代理(Gin 的 `SetTrustedProxies`、Uvicorn 的 `--forwarded-allow-ips`)。
|
||
- **负载均衡**:`upstream` 中列多个 server,默认轮询;还有 `weight` 权重、`least_conn` 最少连接、`ip_hash` 同一 IP 固定到同一台。`max_fails` / `fail_timeout` 做被动健康检查。
|
||
- **静态资源**:`root` 拼接路径(`root /var/www;` + `/a.js` → `/var/www/a.js`),`alias` 替换路径;SPA 用 `try_files $uri $uri/ /index.html;` 把前端路由回退到 index.html;带哈希的静态文件设长缓存,`index.html` 不缓存;开启 `gzip`。
|
||
|
||
## 案例
|
||
|
||
前后端分离部署在一台服务器:`/` 是 Vue / React 打包后的静态文件,`/api/` 转发到两台 Go API 实例,`/ai/` 转发到 Python AI 服务。刷新 `/orders/42` 时出现 404,原因是没有配置 `try_files` 回退。
|
||
|
||
## 代码示例
|
||
|
||
```nginx
|
||
upstream api {
|
||
least_conn;
|
||
server 127.0.0.1:8081 max_fails=3 fail_timeout=10s; # 示例:两个后端实例
|
||
server 127.0.0.1:8082 max_fails=3 fail_timeout=10s;
|
||
keepalive 32; # 和后端保持长连接
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name example.com;
|
||
|
||
root /var/www/app/dist;
|
||
gzip on;
|
||
gzip_types text/css application/javascript application/json image/svg+xml;
|
||
|
||
location / {
|
||
try_files $uri $uri/ /index.html; # SPA 前端路由回退
|
||
}
|
||
location = /index.html {
|
||
add_header Cache-Control "no-cache"; # 入口文件不缓存,发版立即生效
|
||
}
|
||
location /assets/ {
|
||
expires 1y; # 文件名带哈希,可以长缓存
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
location /api/ {
|
||
proxy_pass http://api; # 不带路径:/api/users 原样转发
|
||
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 3s;
|
||
proxy_read_timeout 30s;
|
||
}
|
||
}
|
||
```
|
||
|
||
可在本机用真实 Nginx 验证的完整配置和测试见 [examples/gateway](examples/gateway/)。修改配置后先 `nginx -t` 检查语法,再 `nginx -s reload` 平滑重载。
|
||
|
||
## 易错点
|
||
|
||
- **`proxy_pass` 带不带斜杠搞混**:后端收到的路径多了或少了 `/api`,表现为 404。
|
||
- **`root` 和 `alias` 搞混**:`location /static/ { alias /data/files/; }` 中 alias 末尾的斜杠要和 location 一致。
|
||
- **SPA 没配 `try_files`**:刷新非首页路由 404。
|
||
- **`index.html` 被长缓存**:发版后用户还在用旧页面、引用已删除的旧资源。
|
||
- **后端拿到的 IP 全是 Nginx 的**:没传 `X-Forwarded-For` 或后端没配置可信代理;反过来,盲目信任该头会被伪造。
|
||
- **改完配置直接 reload 不先 `nginx -t`**:语法错误时 reload 失败(旧配置继续运行),但如果是 restart 就直接起不来了。
|
||
|
||
## 面试常问
|
||
|
||
见 [Nginx 面试题](interview.md):正向和反向代理、location 优先级、负载均衡策略、proxy_pass 斜杠问题。
|