📌 前言


很多刚接触项目部署的小伙伴,在本地开发时往往是这样操作的:前端跑在 localhost:3000(或者 80 端口),后端跑在 localhost:8080,中间搞个跨域配置,玩得不亦乐乎。

但是一到服务器部署,面对 Nginx 就懵圈了:

“平时我访问 localhost:8080/index.html 才能看到页面,配置了 Nginx 代理后,凭什么我直接敲个域名或 localhost 就能直接访问了?内部到底是怎么转发的?”

这个问题非常经典,是每个后端/全栈开发必经的“顿悟时刻”。今天,我们就用大白话把 Nginx 转发的底层逻辑扒个底朝天!

🧠 一、先搞懂一个关键概念:反向代理

1.1 没有 Nginx 时,你是怎么访问的?

你在浏览器里输入 localhost:8080/index.html,这意味着:

  • 直接 和 Spring Boot 内嵌的 Tomcat 对话
  • 必须知道 它住在 8080 这个"房间号"
  • 如果前端部署在另一个端口(比如 3000),还会遇到 跨域问题

🏠 类比一下:就像你要找某个公司的张三,你得自己知道他在 18楼 808室,然后自己坐电梯上去敲门。


在这里插入图片描述

1.2 有了 Nginx 后,发生了什么?

Nginx 就像公司大楼的 前台接待员

  • 你只需要走到 大门口localhost,默认 80 端口)
  • 前台会根据你的来意,自动帮你转接
    • 想看网页?→ 直接给你拿前端文件
    • 想调接口?→ 帮你转接到后端 8080
  • 你全程不需要知道任何"房间号"

这就是所谓的 反向代理(Reverse Proxy)——客户端不直接访问真实服务,而是通过一个中间层(Nginx)来统一调度。


1.3 一张图看懂整体流程

                         ┌─────────────────────────────────────────┐
                         │              Nginx(监听 80 端口)        │
                         │                                         │
  浏览器                  │   请求路径                 转发目标       │
  ──────────────────►    │   /           ──►  前端静态文件(本地磁盘)│
  http://localhost/      │   /api/**     ──►  Spring Boot(8080)   │
  http://localhost/api/* │                                         │
                         └─────────────────────────────────────────┘
                                    │                    │
                                    ▼                    ▼
                           ┌──────────────┐    ┌──────────────────┐
                           │  /var/www/    │    │  localhost:8080  │
                           │  html/my-app │    │  (Spring Boot)   │
                           │  index.html  │    │  /api/user/1     │
                           │  css/js/img  │    │  /api/order/...  │
                           └──────────────┘    └──────────────────┘

在这里插入图片描述


🔧 二、两种部署方式下的 Nginx 配置详解

实际项目中,前端和后端的"打包方式"不同,Nginx 的配置也不一样。下面分两种情况讲清楚。


2.1 方式一:前后端分离部署(⭐ 生产环境推荐)

这是目前最主流的部署方式:

组件 打包方式 部署位置
前端(Vue/React) npm run build → 生成 dist 目录 复制到服务器 /var/www/html/my-app
后端(Spring Boot) mvn package → 生成 .jar 文件 java -jar app.jar,监听 8080
Nginx 监听 80 端口,负责"调度"

在这里插入图片描述

✅ Nginx 配置文件
# /etc/nginx/conf.d/my-app.conf

server {
    # ① 监听 80 端口(HTTP 默认端口,浏览器输入域名时自动走这里)
    listen       80;
    server_name  localhost;  # 生产环境换成你的域名,如 www.example.com

    # ② 前端静态资源:所有"非 API"请求都走这里
    location / {
        # 前端打包文件存放的目录
        root   /var/www/html/my-app;

        # 关键配置!按顺序尝试:
        #   1. 精确匹配文件(如 /css/style.css)
        #   2. 匹配目录
        #   3. 都没有?回退到 index.html(SPA 单页应用的路由全靠这个!)
        try_files $uri $uri/ /index.html;
    }

    # ③ 后端 API 代理:所有以 /api/ 开头的请求转发给 Spring Boot
    location /api/ {
        # 核心指令!把请求转发到后端服务
        proxy_pass http://localhost:8080;

        # 透传客户端真实信息给后端(推荐加上)
        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;
    }

    # ④ 错误页面(可选)
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
🔍 请求流程拆解

场景1:用户打开首页

用户输入:http://localhost/

  → 浏览器发起请求:GET /
  → Nginx 匹配到 location /
  → 去 /var/www/html/my-app 目录找 index.html
  → 找到了!直接返回给浏览器 ✅
  → 浏览器渲染页面,同时加载 CSS/JS 等静态资源(同理走 location /)

场景2:前端页面调用后端接口

前端代码:axios.get('/api/user/1')

  → 浏览器发起请求:GET http://localhost/api/user/1
  → Nginx 匹配到 location /api/(更精确,优先匹配)
  → Nginx 转发请求到 http://localhost:8080/api/user/1
  → Spring Boot 处理请求,返回 JSON 数据
  → Nginx 拿到响应,原样返回给浏览器 ✅

💡 注意:整个过程中,浏览器只和 localhost:80(Nginx)通信,完全不知道 8080 的存在。所以从浏览器视角看,前端页面和 API 接口是 同源 的 —— 跨域问题自然就不存在了!


在这里插入图片描述

2.2 方式二:前后端一体化部署(适合小项目/学习)

有些项目会把前端的 HTML/CSS/JS 直接放在 Spring Boot 的 src/main/resources/static 目录下,然后一起打成一个 JAR 包。

此时 Spring Boot 既当爹又当妈:既处理 API 请求,又提供静态页面。

Nginx 的工作就简单多了 —— 当个"传话筒",所有请求无脑转发就行

✅ Nginx 配置文件
server {
    listen       80;
    server_name  localhost;

    # 简单粗暴:所有请求一律转发给 Spring Boot
    location / {
        proxy_pass http://localhost:8080;

        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;
    }
}
🔍 请求流程拆解
用户输入:http://localhost/

  → Nginx 收到请求 GET /
  → 转发到 http://localhost:8080/
  → Spring Boot 内嵌的 Tomcat 处理
  → 返回 static/index.html 的内容
  → Nginx 透传给浏览器 ✅

前端调用:axios.get('/api/user/1')

  → Nginx 收到请求 GET /api/user/1
  → 转发到 http://localhost:8080/api/user/1
  → Spring Boot 的 @RestController 处理
  → 返回 JSON
  → Nginx 透传给浏览器 ✅

在这里插入图片描述


⚖️ 三、两种方式对比,怎么选?

对比维度 🥇 前后端分离部署 🥈 前后端一体化部署
Nginx 职责 静态文件托管 + API 转发 纯转发(传话筒)
Spring Boot 职责 只管 API 业务逻辑 API + 静态文件,啥都管
静态资源性能 极快(Nginx 天生擅长) 🐢 较慢(Tomcat 处理静态文件效率低)
部署灵活性 ✅ 前后端独立部署、独立更新 ❌ 改个前端样式都要重新打 JAR 包
扩展性 ✅ 前后端可独立扩容 ❌ 只能整体扩容
配置复杂度 稍复杂(需区分路径) 非常简单
适用场景 生产环境、团队协作 个人小项目、学习练手

📢 结论:生产环境强烈推荐 前后端分离部署。Nginx 处理静态资源的能力是 Tomcat 的 几十倍,而且前后端解耦后,团队协作效率会大幅提升。


在这里插入图片描述

🎯 四、关于跨域问题"自动消失"的原理

很多同学好奇:为什么配了 Nginx 之后,前端调后端接口不再报跨域错误了?

这就要理解浏览器的 同源策略(Same-Origin Policy)

同源 = 协议 + 域名 + 端口 三者完全一致

场景 前端地址 API 地址 是否同源
没有 Nginx http://localhost:3000 http://localhost:8080/api ❌ 端口不同,跨域!
有了 Nginx http://localhost (80) http://localhost/api (80) ✅ 完全同源,没问题!

关键在于:浏览器看到的始终是 localhost:80,它根本不知道 /api 的请求被 Nginx 偷偷转发到了 8080。在浏览器眼里,一切都是同一个"源"。


在这里插入图片描述

🧩 五、补充:proxy_set_header 那几行到底在干嘛?

你可能注意到配置里总有这么几行"透传头信息"的代码,很多人直接复制粘贴但不理解它的作用:

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 可能是 localhost:8080,而不是用户实际访问的域名
X-Real-IP 把客户端真实 IP 传给后端 后端通过 request.getRemoteAddr() 拿到的是 Nginx 的 IP(127.0.0.1),而非用户真实 IP
X-Forwarded-For 记录整个代理链路的 IP 多层代理时无法追溯请求来源
X-Forwarded-Proto 告诉后端原始请求是 HTTP 还是 HTTPS 后端无法正确判断协议,可能影响重定向逻辑

🔑 一句话总结:这几行配置确保后端能获取到 用户的真实信息,而不是 Nginx 代理服务器的信息。生产环境 务必加上


✍️ 总结

整篇文章的核心其实就一句话:

Nginx 反向代理 = 在浏览器和后端服务之间加了一个"智能前台",统一入口、按路径分发、隐藏内部细节。

再回顾一下关键要点:

  1. listen 80 —— 让 Nginx 监听默认端口,用户无需输入端口号
  2. location / —— 匹配前端静态资源请求
  3. location /api/ —— 匹配后端 API 请求
  4. proxy_pass —— 核心指令,实现请求转发
  5. try_files —— SPA 单页应用的救命配置,保证前端路由正常工作
  6. 同源效果 —— 浏览器只看到 80 端口,跨域问题自然消失

更多推荐