--- 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 斜杠问题。