🚀🚀🚀2026年还不会Nginx?

0 阅读9分钟

做前端迟早会遇到这几件事:跨域、本地页面连测试接口、在测试页上调试自己改的 JS。多半会用到 Nginx——轻、稳、配置也好懂。下面从 proxy_passlocation 说起,用两个联调场景讲清楚怎么起;后面再补负载均衡和网关那套生产配置。


摘要

  • Nginx 既能托管静态文件,也能当反向代理,把请求转到别的机器或端口。
  • 联调时尽量让浏览器只访问一个地址(一般是 localhost:8080),页面走本地,接口走测试,同源就不折腾 CORS 了。
  • 场景 A:本地起前端,接口代理到测试环境。
  • 场景 B:页面还是测试环境的,JS/CSS 走本地。
  • 多台后端用 upstream 做负载均衡;统一入口、HTTPS、限流这些算网关活,Nginx 也能干。
  • 改完配置:nginx -t 检查一下,再 nginx -s reload。Windows 不想装 Nginx,Docker 跑一份就行。

一、Nginx 是干啥的

能干啥说明啥时候用
托管静态文件直接吐 HTML、JS、CSS生产环境放 dist/
反向代理收到请求,转给后面的服务,再把结果返回/api 转后端;本地联调测试接口
负载均衡后面好几台机器,帮忙分摊请求API 多副本
当网关使按路径分流、改 Header、限流、接 HTTPS统一入口

正向代理和反向代理,别搞混:

  • 正向代理:你知道有个代理(比如翻墙),它替去访问网站。
  • 反向代理:你不知道后面有几台机器,Nginx 替服务端接请求再分发。浏览器只认 Nginx 这一个地址。

联调里基本都是在玩反向代理。

二、配置怎么读

Nginx 配置一般是 httpserverlocation 三层套娃。

http {
    # 全局:日志格式、gzip、upstream 等

    upstream test_api {
        server test-api.example.com:443;
    }

    server {
        listen 8080;                    # 监听端口
        server_name localhost;          # 匹配的域名(本地联调常用 localhost)

        location / {
            # 路径怎么匹配,看下面 2.1
        }

        location /api/ {
            proxy_pass https://test-api.example.com/;
        }
    }
}

2.1 location 怎么匹配

写法啥意思例子
location /api/前缀匹配/api/user 能命中
location = /health必须完全一样只匹配 /health
location ~ \.js$正则.js 结尾
location ^~ /static/前缀匹配,且不再走后面的正则静态目录常用

匹配优先级(记个大概就行):= > ^~ > 正则 ~ / ~* > 普通前缀 /

2.2 proxy_pass 末尾的 /(坑最多)

联调前先搞清楚测试环境接口的真实路径,再决定 proxy_pass 要不要带路径、末尾要不要加 /

# 情况 1:proxy_pass 带 URI 路径(含末尾 /)
location /api/ {
    proxy_pass https://test.example.com/v1/;
}
# 请求 /api/user/list  →  https://test.example.com/v1/user/list
# (去掉 location 前缀 /api/,拼到 proxy_pass 后面)

# 情况 2:proxy_pass 只有域名/主机,无路径
location /api/ {
    proxy_pass https://test.example.com;
}
# 请求 /api/user/list  →  https://test.example.com/api/user/list
# (完整 URI 原样丢过去)

2.3 反向代理常写的几行

location /api/ {
    proxy_pass https://test-api.example.com/;

    proxy_set_header Host test-api.example.com;   # 上游按 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 10s;
    proxy_read_timeout 60s;

    # HTTPS 上游若证书有问题(仅开发环境)
    # proxy_ssl_verify off;
}

三、安装与本地启动

3.1 Windows:推荐 Docker(免安装)

项目根目录准备 nginx/dev.conf,用官方镜像挂载配置:

docker run --rm -p 8080:8080 `
  -v ${PWD}/nginx/dev.conf:/etc/nginx/conf.d/default.conf:ro `
  nginx:alpine

浏览器访问 http://localhost:8080。改配置后重启容器,或在容器内执行 nginx -s reload

3.2 macOS / Linux

# macOS
brew install nginx

# Ubuntu / Debian
sudo apt install nginx

# 校验配置
nginx -t

# 热加载(不中断连接)
nginx -s reload

默认站点配置常见路径:

  • Linux:/etc/nginx/nginx.conf/etc/nginx/conf.d/*.conf
  • macOS(Homebrew):/opt/homebrew/etc/nginx/
  • 项目内自建:-c /path/to/nginx.conf 指定配置文件

四、场景 A:本地前端 + 测试环境接口

4.1 要解决啥

前端本地 Vite,npm run devhttp://localhost:5173
接口测试环境 https://test-api.example.com
烦在哪直连测试 API 跨域;Cookie 域对不上;想在本地 UI 上看真实数据

目标:浏览器只开 http://localhost:8080,页面来自本地 Vite,/api/** 交给 Nginx 转到测试环境。

4.2 配置示例 nginx/dev-local-api.conf

测试环境 API 前缀是 /v1/,本地前端统一请求 /api/(跟线上一致)。

server {
    listen 8080;
    server_name localhost;

    # 1. 接口:转发到测试环境(Header 写法见 2.3)
    location /api/ {        proxy_pass https://test-api.example.com/v1/;

        proxy_set_header Host test-api.example.com;
        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 https;

        # 若测试环境要 Cookie / 登录态,把浏览器 Cookie 原样带上(默认会带)
        proxy_pass_request_headers on;
    }

    # 2. Vite 热更新 WebSocket(否则 HMR 可能失败)
    location /@vite/client {
        proxy_pass http://127.0.0.1:5173;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }

    location /@fs/ {
        proxy_pass http://127.0.0.1:5173;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }

    # 3. 其余请求:本地前端
    location / {
        proxy_pass http://127.0.0.1:5173;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

/@vite/client/@fs/ 干嘛用的

走 8080 代理时,只配 location / 页面多半能开,但热更新(HMR)容易挂。下面两个是专门给 Vite 开发模式用的。 1. location /@vite/client — HMR 客户端 + WebSocket

Vite 开发模式会在 HTML 里注入:

<script type="module" src="/@vite/client"></script>

该脚本跟 Vite 建 WebSocket 长连接:你改代码,它推更新,不用整页 F5。

HMR 不是普通 HTTP,得带 Upgrade 头,不然 WebSocket 握不上。常见情况:页面能开,改代码没反应。 2. location /@fs/ — 访问项目目录外的文件

Vite 用 /@fs/ 读项目根目录外面的文件,比如 monorepo 里别的包。请求 http://localhost:8080/@fs/... 得转到 Vite,不然 404。 location / 的分工

浏览器 http://localhost:8080
        │
        ├─ /@vite/client   → HMR(WebSocket)
        ├─ /@fs/           → Vite 读盘外文件
        ├─ /api/           → 测试环境接口
        └─ /               → 其余页面、JS、CSS → Vite

/@vite/client/@fs/ 比通用的 location / 更具体,会优先匹配,避免 WebSocket 头被「粗糙」转发。

能开页面吗热更新
只配 location /一般能开时灵时不灵
加上面两个能开

只有开发模式才有这俩路径,生产 dist 里没有,上线不用配。

4.3 前端怎么配(Vite)

vite.config.ts 里可以不写 server.proxy 了,交给 Nginx。接口 baseURL 指同源就行:

// .env.development
// VITE_API_BASE=/api

export default defineConfig({
  server: {
    port: 5173,
    // 要是习惯直接开 5173,可以留着 dev proxy 当备用
  },
});

业务代码:

const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE || '/api',
});

4.4 怎么起

终端 1 — 前端

cd frontend
npm run dev
# Vite 默认 http://localhost:5173

终端 2 — Nginx

cd F:\your-project
docker run --rm -p 8080:8080 `
  -v ${PWD}/nginx/dev-local-api.conf:/etc/nginx/conf.d/default.conf:ro `
  --add-host=host.docker.internal:host-gateway `
  nginx:alpine

Nginx 跑在 Docker 里的话,127.0.0.1:5173 连的是容器自己,不是宿主机。改成 host.docker.internal:5173(Windows / Mac Docker Desktop)。 修正 Docker 版 upstream:

location / {
    proxy_pass http://host.docker.internal:5173;
    # ... 同上
}

浏览器开 http://localhost:8080。别直接开 5173,不然 /api 到不了测试环境。

4.5 登录态怎么办

情况做法
测试环境用 Cookie 会话浏览器访问 localhost:8080 时,登录接口也走 /api/,Set-Cookie 的 Domain 若是测试域,可能需要测试环境配合改 Cookie 域,或改用 Token 放 Header
用 Bearer Token本地 .env 填测试 Token;axios 拦截器统一加 Authorization
测试环境校验 Referer / OriginNginx 加 proxy_set_header Origin https://test.example.com;(按实际要求)

五、场景 B:测试页面 + 本地静态资源

5.1 要解决啥

HTML还是测试环境 https://test.example.com 的(配置、嵌入、路由都是真的)
JS / CSS想走本地 http://localhost:5173,改完立刻看效果
啥时候用在测试页上调试本地 bundle;或测试数据 + 本地 UI

目标:地址栏可以是测试域名(或 hosts 指到本地 Nginx),但 /assets/*.js*.css 从本地出。

5.2 配置示例 nginx/dev-test-static-local.conf

先看清测试环境静态资源路径。Vite 一般是 /assets/index-xxxxx.js,老项目可能是 /static/js/main.js

server {
    listen 8080;
    server_name localhost;

    # 测试环境页面主机(HTML、接口等)
    set $test_web https://test.example.com;
    set $test_api https://test-api.example.com;

    # 1. 本地静态资源(优先级要高,放在前面)
    location /assets/ {
        proxy_pass http://host.docker.internal:5173/assets/;
        proxy_http_version 1.1;
        proxy_set_header Host localhost:5173;
        proxy_set_header Accept-Encoding "";   # 避免压缩导致 source map 错乱(按需)
    }

    # 2. 按后缀匹配 JS/CSS(测试 HTML 里若引用 /static/xxx.js)
    location ~* \.(js|css|map)$ {
        proxy_pass http://host.docker.internal:5173;
        proxy_http_version 1.1;
        proxy_set_header Host localhost:5173;
    }

    # 3. Vite 开发态特有路径
    location ~ ^/(@vite|@fs|node_modules)/ {
        proxy_pass http://host.docker.internal:5173;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host localhost:5173;
    }

    # 4. 接口仍走测试(可选)
    location /api/ {
        proxy_pass $test_api/v1/;
        proxy_set_header Host test-api.example.com;
        proxy_ssl_server_name on;
    }

    # 5. 默认:HTML 和其他走测试环境
    location / {
        proxy_pass $test_web;
        proxy_set_header Host test.example.com;
        proxy_ssl_server_name on;

        # 可选:改写 HTML 里的资源引用(仅当测试 HTML 写死绝对 URL 时需要)
        # sub_filter 'https://test.example.com/assets/' 'http://localhost:8080/assets/';
        # sub_filter_once off;
        # sub_filter_types text/html;
    }
}

5.3 本地 Vite 要对齐的事

测试 HTML 里如果是带 hash 的文件名(index-a1b2c3.js),本地 dev 往往是另一套路径,会 404。

几个办法:

方案说明
A. 本地 vite build --watch + 静态服务构建产物路径与测试一致,Nginx 把 /assets/ 指到本地 dist/assets/(用 npm run build -- --watch
B. 改测试 HTML 引用仅调试窗口内用浏览器插件或 Nginx sub_filter 把 script src 改成本地入口(脆弱,临时用)
C. 本地也跑测试环境同款构建从测试拉一份 index.html,把 script 改成 @vite/client + /src/main.tsx(适合深度调试)

方案 A 示例(本地 watch 构建,路径与测试一致):

# 终端 1
npm run build -- --watch

# 终端 2:用 nginx 或任意静态服务托管 dist
npx serve dist -p 5173

Nginx 里 /assets/ 的写法同 5.2,不用另配。

这样测试 HTML 里的 <script src="/assets/index-xxx.js"> 会打到本地 watch 构建出来的同名文件。

5.4 用 hosts 假装测试域名(可选)

页面写死了 test.example.com(Cookie 域、location.hostname 之类)时可以这么干:

  1. 编辑 hosts:127.0.0.1 test.example.com
  2. Nginx listen 8080server_name test.example.com
  3. 浏览器访问 http://test.example.com:8080

HTTPS 测试域还得搞证书,本地一般用 8080 + HTTP,或者 mkcert 自签,这里不展开了。

5.5 启动顺序

先起本地静态(npm run dev,或 npm run build -- --watch + npx serve dist -p 5173)。Docker 启动 Nginx 同 4.4,配置文件换成 dev-test-static-local.conf

开 DevTools → Network 看一眼:HTML 来自测试,JS/CSS 应该走本地代理。

六、负载均衡:后面好几台 API

6.1 啥时候需要

一台扛不住,或者想一台挂了另一台顶上,就在后面摆多台相同服务。Nginx 站在门口帮忙分请求。

                    ┌─► api-1:8080
浏览器 ──► Nginx ───┼─► api-2:8080
                    └─► api-3:8080

前端不用改,还是请求 https://www.example.com/api/...,不知道后面有几台。

6.2 upstream 怎么写

http 里定义一组上游服务器,再在 location 里用变量引用:

http {
    upstream api_cluster {
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;
        server 192.168.1.13:8080;
    }

    server {
        listen 80;

        location /api/ {
            proxy_pass http://api_cluster/;   # 注意:upstream 名后一般带 /
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

6.3 怎么分配请求

策略怎么配啥效果适合啥
轮询(默认)不用写挨个来机器一样、无状态 API
加权weight=3权重大的多分机器配置不一样
最少连接least_conn;给连接少的长连接、耗时差大
IP 哈希ip_hash;同一 IP 固定一台简易粘会话(不太靠谱)
URI 哈希hash $request_uri consistent;同 URI 落同节点缓存友好
示例:加权 + 备用机
upstream api_cluster {
    least_conn;

    server 192.168.1.11:8080 weight=5;
    server 192.168.1.12:8080 weight=5;
    server 192.168.1.13:8080 weight=2;   # 配置较低,少分点

    server 192.168.1.99:8080 backup;     # 前两台都挂了才启用
}

示例:IP 粘滞(简易「登录态跟机器走」)

upstream api_cluster {
    ip_hash;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

真要粘会话,不如 JWT 或 Redis 共享 Session。ip_hash 换 Wi‑Fi、走 NAT IP 就变了。

6.4 挂了怎么踢掉

开源 Nginx 没有 K8s 那种主动探活,靠转发失败次数:

upstream api_cluster {
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}
参数含义
max_fails=3连续失败 3 次,暂时标记不可用
fail_timeout=30s30 秒内不再往该节点转发;过后再试

6.5 验证有没有分到多台

多敲几次 curl http://localhost/api/health,后端如果返回 hostname,应该能在三台之间轮换。完整「静态 + 多副本 API」配置见 7.7。

6.6 灰度:先放一点流量到新版本新版本 API 先接少量流量:

upstream backend {
    server api-old:8080 weight=9;   # 90% 流量
    server api-new:8080 weight=1;   # 10% 流量
}

没问题再把 api-newweight 往上加,最后干掉 api-old


七、网关:统一门口 + 管流量

7.1 跟反向代理差在哪

反向代理网关(Nginx 能干轻量版)
核心转发统一入口 + 路由 + 限流之类
常见活proxy_pass按路径分流、HTTPS、限流、改 Header
复杂度再复杂就上 Kong、APISIX、云 LB

前面场景 A/B 其实就是反向代理;上线后同一台 Nginx 往往静态、反代、负载均衡、网关路由全包了。

7.2 一个域名,不同路径进不同服务

前端最常见:用户、订单、支付各一套服务,外面只暴露一个 api.example.com

upstream user_service   { server user-svc:8080; }
upstream order_service  { server order-svc:8080; }
upstream pay_service    { server pay-svc:8080; }

server {
    listen 443 ssl;              # HTTPS 默认 443,ssl 表示在这开 TLS
    server_name api.example.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;   # 证书在 Nginx 机器上
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    location /api/user/ {
        proxy_pass http://user_service/;
    }

    location /api/order/ {
        proxy_pass http://order_service/;
    }

    location /api/pay/ {
        proxy_pass http://pay_service/;
    }
}

前端只管请求 https://api.example.com/api/...,后面几个微服务不用管。

https://api.example.com
        │
        ├─ /api/user/*   → user-svc
        ├─ /api/order/*  → order-svc
        └─ /api/pay/*    → pay-svc

对外 HTTPS、对内 HTTP 时,证书只装在 Nginx 上,后面微服务一般不用装。浏览器 ↔ Nginx 走加密,Nginx ↔ 内网走 HTTP 即可,记得加 proxy_set_header X-Forwarded-Proto https 告诉后端外面是 HTTPS。本地联调用 8080 就够,上线才要 443 ssl

7.3 限流按 IP 限制频率,得先定义 limit_req_zone

http {
    # 定义共享内存区:按 IP 限流,每秒平均 10 次,桶容量 20
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    server {
        location /api/ {
            limit_req zone=api_limit burst=20 nodelay;
            proxy_pass http://backend/;
        }
    }
}
参数含义
rate=10r/s平均每秒 10 个请求
burst=20允许短时突发最多 20 个
nodelayburst 满了直接 503,不排队

超了 Nginx 回 503,前端可以提示「点太快了」。

7.4 改 Header、透传 Token

location /api/ {
    proxy_pass http://backend/;

    # 透传前端 Authorization
    proxy_set_header Authorization $http_authorization;

    # 注入链路追踪 ID(示例)
    proxy_set_header X-Request-Id $request_id;

    # 隐藏内网信息(可选)
    proxy_hide_header X-Powered-By;
}

管理接口加 IP 白名单

location /api/admin/ {
    allow 10.0.0.0/8;
    allow 192.168.0.0/16;
    deny all;

    proxy_pass http://admin_service/;
}

7.5 网关统一配 CORS(看情况)

好几个后端各自配 CORS 很乱,可以在 Nginx 一层搞(真的需要再搞):

location /api/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "Authorization, Content-Type";
        add_header Access-Control-Max-Age 86400;
        return 204;
    }

    add_header Access-Control-Allow-Origin $http_origin always;
    proxy_pass http://backend/;
}

联调还是场景 A 那种同源代理省事,浏览器根本不会触发 CORS。网关层 CORS 一般是正式环境、多端多域又没法同源时才上。

7.6 生产入口配一版长的(含静态托管)```nginx

http { limit_req_zone $binary_remote_addr zone=global:10m rate=50r/s;

upstream api_v1 {
    least_conn;
    server api-a:8080 weight=5;
    server api-b:8080 weight=5;
}

upstream api_v2 {
    server api-v2-canary:8080;
}

server {
    listen 443 ssl;
    server_name www.example.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    root /usr/share/nginx/html;

    gzip on;
    gzip_types text/plain text/css application/javascript application/json;

    # 静态资源 + SPA 路由
    location / {
        try_files $uri $uri/ /index.html;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    # 稳定版 API + 负载均衡 + 限流        location /api/v1/ {
        limit_req zone=global burst=100 nodelay;
        proxy_pass http://api_v1/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }

    # 灰度:新版本单独路径或后续可改为 map 分流
    location /api/v2/ {
        proxy_pass http://api_v2/;
    }

    location /ws/ {
        proxy_pass http://api_v1/ws/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

}


上面这一坨里都有啥:

| 想干啥 | 看哪 |
|--------|------|
| 负载均衡 | `upstream api_v1` + `least_conn` / `weight` |
| 按路径分流 | `/api/v1/``/api/v2/` |
| HTTPS | `listen 443 ssl` + 证书两行 |
| 限流 | `limit_req_zone` + `limit_req` |
| 静态 + API + 缓存 | `root` + `try_files` + `gzip` + `expires` |

---

## 八、WebSocket

Upgrade 头在 4.2 的 Vite HMR 里已经配过,通用 `/ws/` 写法一样:```nginx
location /ws/ {
    proxy_pass http://backend:8080/ws/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
}

Vite HMR、Socket.io、协作编辑那些都一样。


九、踩坑对照

现象可能原因处理
502 Bad Gateway上游挂了;Docker 里用了 127.0.0.1 指宿主机改成 host.docker.internal;确认 Vite 监听 0.0.0.0
404,路径多一段或少一段proxy_pass 末尾 / 搞错了看 2.2 节
CORS 仍报错浏览器绕过了 Nginx,直连 5173 或测试 API统一从 localhost:8080 访问HMR 不生效未代理 /@vite/client 或 WebSocket 头缺失补全 Upgrade / Connection
静态资源仍来自测试location 优先级或正则未命中用 Network 看 URL,调整 ^~ /assets/ 顺序
登录态丢失Cookie Domain 是测试域hosts 模拟域名,或改 Token 方案
nginx -t 报错分号、括号、重复 listen按行号改;include 文件也检查

查问题

tail -f /var/log/nginx/error.log
curl -I http://localhost:8080/assets/index.js
curl http://localhost:8080/api/health

十、场景速查

你想做什么页面来源接口来源静态 JS/CSS 来源
纯本地 mock本地 Vite本地 mock / json-server本地 Vite
场景 A:本地 UI + 测试数据本地 ViteNginx → 测试 API本地 Vite
场景 B:测试页面 + 本地静态Nginx → 测试 Web测试 API(或 Nginx 转发)Nginx → 本地 dist/Vite
生产 + 负载均衡Nginx rootNginx → upstream 多副本Nginx root
生产 + 网关分流Nginx root/api/user/api/order 分服务Nginx root

十一、常用命令

校验、热加载、nginx -t 见第三节。Docker 联调启动见 4.4。

场景命令
校验配置nginx -t
热加载nginx -s reload
停止nginx -s stop
看请求走哪DevTools → Network,或 curl -v

十二、官方文档


域名都是示例,换成你们测试/生产真实地址;HTTPS 上游如果要客户端证书,再加 proxy_ssl_certificate 等配置。