Nginx反向代理实战:前后端分离项目部署与跨域解决方案

Nginx反向代理实战:前后端分离项目部署与跨域解决方案
1. 项目概述为什么要把前后端“绑”在一起前后端分离的架构现在已经是主流了前端用Vue、React后端用Spring Boot、Go各自独立开发、独立部署清爽得很。但部署上线时一个很现实的问题就来了前端项目跑在8080端口后端API在8081端口。用户访问你的网站得记住两个地址吗显然不行。更麻烦的是浏览器出于安全考虑的同源策略直接从前端页面发请求到不同端口的后端大概率会报CORS跨域错误你得在后端吭哧吭哧配一堆CORS头。所以一个常见的需求就是把前后端“聚合”起来让用户感觉在访问同一个服务。最理想的方案就是用户只访问一个地址比如https://yourdomain.com所有前端页面//login/dashboard和所有后端API请求/api/xxx都通过这个入口进来由“总管家”Nginx来智能分发。这样做的好处太多了对外只有一个入口便于管理天然解决了开发环境的跨域问题可以统一做SSL证书、限流、缓存、负载均衡还能隐藏后端服务的真实端口和路径提升安全性。这个“总管家”的角色Nginx当仁不让。它轻量、高性能反向代理功能更是其看家本领。这次要聊的就是如何用Nginx的配置优雅地实现这个“聚合”无论是部署在同一台服务器的不同端口还是通过一个域名来统一访问。2. 核心思路与架构设计拆解在动手写配置之前得先把脑子里的蓝图画清楚。前后端分离项目通过Nginx聚合核心思路就一个词反向代理。但具体怎么“反”根据你的部署环境和需求主要有两种经典模式。2.1 模式一IP端口直连式同机部署这是最简单、最直观的场景。假设你把前后端都部署在了同一台服务器或同一个Docker宿主机上。前端服务比如用Node.js的serve或nginx本身托管静态文件运行在http://localhost:3000。后端服务比如一个Spring Boot应用运行在http://localhost:8080。你的服务器对外IP是192.168.1.100。目标用户访问http://192.168.1.100:80或某个自定义端口如9000时能同时访问前端页面和后端接口。设计思路 Nginx监听服务器的80端口。它根据用户请求的URL路径特征决定将请求转发给谁。当请求路径是//index.html/static//assets/等典型的前端静态资源路径时Nginx将请求代理到本机的3000端口前端服务。当请求路径以/api/开头时Nginx识别这是后端API请求将请求代理到本机的8080端口后端服务。这样对于浏览器而言它始终只和192.168.1.100:80通信完全感知不到后端8080端口的存在跨域问题迎刃而解。2.2 模式二域名路由式生产环境首选生产环境我们通常不会让用户记IP而是用域名。比如你的网站域名是www.myapp.com。设计思路 这个模式下Nginx的配置逻辑和模式一类似但绑定的是域名。我们会在Nginx配置中指定server_name www.myapp.com;。所有发往这个域名的请求都由这台Nginx处理。 路径分发规则不变/去前端/api/去后端。但这里有一个关键点前端代码里的API请求地址需要配置为相对路径比如axios.defaults.baseURL /api而不是http://api.myapp.com:8080。这样浏览器会自动向当前域名www.myapp.com的/api路径发起请求正好被Nginx捕获并转发。这种模式的优势在于你可以非常方便地扩展。如果后端压力大了你可以把后端服务迁移到另一台服务器只需修改Nginx的proxy_pass地址比如从http://localhost:8080改为http://backend-server-ip:8080前端代码一行都不用改。2.3 关键决策点静态资源由谁托管这里有一个重要的技术选型前端打包后的静态文件HTML CSS JS 图片由谁来托管方案A由Nginx直接托管。这是最推荐、性能最好的方式。你将前端打包后的dist文件夹直接放到Nginx的HTML目录下如/usr/share/nginx/html然后通过Nginx的location /配置root指令来直接服务这些文件。对于API请求再用location /api/代理到后端。方案B由前端开发服务器托管。这在纯开发联调阶段有用比如前端用npm run serve跑在3000端口。Nginx的location /会proxy_pass到这个3000端口。但生产环境绝对不要用这个方案因为前端开发服务器不是为了生产环境的高并发和稳定性设计的。我们的配置将以方案ANginx托管静态文件作为生产环境标准来展开因为这才是最合理、最专业的做法。3. Nginx配置核心解析与实操要点理解了架构我们来拆解Nginx配置里的每一个核心指令和区块明白它们为什么这么写以及有哪些坑要注意。3.1 配置骨架server与location区块一个最基本的Nginx配置核心是server块它定义了一个虚拟主机。server { listen 80; # 监听80端口 server_name www.myapp.com; # 域名如果是IP模式这里可以写 _ 或省略 # 这里是具体的路由规则 location / { # 处理前端静态资源 } location /api/ { # 代理后端API请求 } }listen 指定Nginx监听哪个端口。80是HTTP默认端口443是HTTPS。server_name 指定这个server块响应哪个域名的请求。如果用户用IP访问或者你想一个配置块响应所有请求可以设为_下划线或直接不写。location 这是路由规则的核心。它根据请求的URI路径来匹配并定义如何处理。3.2 托管前端静态资源 (location /)我们的目标是让Nginx直接发送前端文件。location / { root /usr/share/nginx/html; # 前端dist文件夹的存放路径 index index.html index.htm; try_files $uri $uri/ /index.html; # 关键支持Vue/React路由 }root 指定静态文件的根目录。你需要把前端项目打包后如npm run build生成的dist文件夹里的所有内容上传到这个目录下。index 指定默认访问的索引文件。try_files这是处理前端路由如Vue Router的history模式或React Router的生命线。它的作用是按顺序尝试寻找文件。先找$uri请求的完整路径对应的文件找不到就找$uri/当作目录找索引文件如果还找不到最后返回/index.html。这样当用户直接访问/dashboard这样的前端路由时Nginx因为找不到dashboard这个文件或目录就会把index.html返回给浏览器由前端的JavaScript路由库来接管并渲染正确的页面。没有这一行刷新非根路径的页面就会得到404。实操心得root指令的路径末尾不要加/。try_files的最后一项/index.html是一个内部重定向它会重新走一遍location /的匹配所以能正确找到index.html文件。3.3 代理后端API请求 (location /api/)这是反向代理的核心配置。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; }proxy_pass 指定请求被转发到哪里。地址末尾的/有大学问。如果写http://localhost:8080/有斜杠那么请求/api/user/login会被转发为http://localhost:8080/user/login/api/这个前缀被移除了。如果写http://localhost:8080无斜杠那么请求会被转发为http://localhost:8080/api/user/login/api/前缀被保留了。绝大多数情况下我们希望移除/api这个前缀因为后端服务通常直接监听在根路径上。所以务必加上末尾的/。proxy_set_header 这组指令至关重要。它修改了转发给后端请求的HTTP头。Host $host 将原始请求的Host头通常是域名传递给后端。有些后端框架如Spring Security需要这个头来做校验。X-Real-IP $remote_addr 将客户端的真实IP传递给后端。否则后端日志里看到的客户端IP都是Nginx服务器的IP如127.0.0.1。X-Forwarded-For $proxy_add_x_forwarded_for 追加客户端IP到X-Forwarded-For链中用于记录请求经过的代理路径。X-Forwarded-Proto $scheme 告诉后端原始的请求协议是HTTP还是HTTPS。如果你的Nginx配置了SSL但后端服务在内网是HTTP这个头能让后端正确构建重定向URL比如生成https://的链接。3.4 处理WebSocket代理如果你的应用使用了WebSocket比如在线聊天、实时通知需要额外的配置因为WebSocket协议在建立连接后需要“升级”。location /api/ws/ { # 假设你的WebSocket端点以 /ws 开头 proxy_pass http://localhost:8080/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; # 以下两行是为了避免WebSocket连接超时断开 proxy_read_timeout 60s; proxy_send_timeout 60s; }关键指令是Upgrade和Connection upgrade它们完成了HTTP到WebSocket协议的升级握手。4. 完整配置实战与部署流程我们结合一个具体的生产环境示例把上面的知识点串起来并走一遍完整的部署流程。4.1 生产环境完整配置示例假设我们有一个Vue Spring Boot的项目域名是app.example.com已申请SSL证书。 我们将配置文件放在/etc/nginx/conf.d/app.conf。# /etc/nginx/conf.d/app.conf # HTTP 重定向到 HTTPS (可选但推荐) server { listen 80; server_name app.example.com; return 301 https://$server_name$request_uri; # 永久重定向 } # 主HTTPS服务器配置 server { listen 443 ssl http2; server_name app.example.com; # SSL证书配置 (你需要替换成自己的证书路径) ssl_certificate /etc/nginx/ssl/app.example.com.crt; ssl_certificate_key /etc/nginx/ssl/app.example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # 前端静态资源 location / { root /var/www/app-frontend; # 你的前端文件存放目录 index index.html; try_files $uri $uri/ /index.html; # 支持前端路由 # 可选的缓存策略提升性能 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; } } # 后端API代理 location /api/ { proxy_pass http://127.0.0.1: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; # 可选的超时设置根据后端接口性能调整 proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; } # 可选代理WebSocket location /api/ws/ { proxy_pass http://127.0.0.1:8080/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_read_timeout 60s; } # 可选静态资源单独目录如果前端打包时publicPath不是/ # location /static/ { # alias /var/www/app-frontend/static/; # expires 1y; # } }4.2 一步步部署操作指南准备前端文件# 在前端项目根目录 npm run build # 或 yarn build # 生成 dist 文件夹将dist文件夹内的所有内容上传到服务器的/var/www/app-frontend目录确保Nginx进程用户通常是nginx或www-data有读取权限。sudo chown -R nginx:nginx /var/www/app-frontend sudo chmod -R 755 /var/www/app-frontend准备后端服务 确保你的Spring Boot应用或其他后端已经在服务器上运行并监听在127.0.0.1:8080。关键一步关闭或正确配置CORS。因为现在所有请求都来自Nginx同源你可以在后端完全禁用CORS配置或者将CORS的允许来源设置为你的域名https://app.example.com。放置Nginx配置 将上面的配置内容写入/etc/nginx/conf.d/app.conf。测试与重载Nginx# 测试配置文件语法是否正确 sudo nginx -t # 如果显示 syntax is ok 和 test is successful # 重载Nginx使配置生效 sudo nginx -s reload # 或者 systemctl reload nginx (Systemd系统)验证打开浏览器访问https://app.example.com应该能看到前端页面。打开浏览器开发者工具的“网络”(Network)选项卡刷新页面。你应该能看到页面加载了index.html和各种静态资源js css这些请求的域名都是app.example.com。在前端页面进行一个登录操作观察网络请求。应该会看到一个向https://app.example.com/api/login发起的请求并且状态码是200或相应的业务码。这证明API请求被正确代理了。5. 常见问题、调试技巧与避坑实录配置看似简单但实际部署时总会遇到各种“妖魔鬼怪”。下面是我踩过坑后总结的排查清单。5.1 前端页面空白或JS/CSS加载404症状能打开index.html但页面空白控制台报JS/CSS文件404。排查检查root路径和文件权限确认/var/www/app-frontend目录下确实有index.html和static或assets等文件夹。用ls -la检查权限。检查前端资源路径打开index.html看里面引用的JS/CSS路径是什么。如果是/static/js/app.js那么Nginx会在root目录下找/static/js/app.js。如果前端打包时配置了publicPath: /admin/那么资源路径会是/admin/static/js/app.js此时你需要调整location /的root或者为/admin/路径单独写一个location块。查看Nginx错误日志tail -f /var/log/nginx/error.log访问页面时看是否有权限拒绝或文件不存在的错误。5.2 访问前端路由非根路径报404症状直接访问首页正常但点击页面内跳转到/dashboard正常然而刷新/dashboard页面或直接输入该地址访问时Nginx返回404。原因与解决这就是前面强调的try_files指令缺失或配置错误。确保location /块内有try_files $uri $uri/ /index.html;。它的作用就是“保底”返回index.html让前端路由接管。5.3 后端API请求报404或502错误症状前端页面正常但所有API调用失败状态码404或502。排查检查proxy_pass地址和末尾斜杠这是最最常见的问题确认后端服务是否真的在http://127.0.0.1:8080运行可以用curl http://127.0.0.1:8080/health测试。确认proxy_pass末尾有/以确保/api/xxx被正确重写为/xxx。检查后端服务是否监听在127.0.0.1有些服务默认只监听localhost即127.0.0.1这没问题。但如果你的服务监听在0.0.0.0:8080用127.0.0.1:8080也能通。502 Bad Gateway这通常意味着Nginx无法连接到后端服务。检查后端进程是否存活端口是否被正确监听netstat -tlnp | grep 8080。检查防火墙是否阻止了Nginx运行在本地对8080端口的访问。查看Nginx访问日志在配置中location /api/里添加access_log可以单独记录代理日志看请求是否转发出去了。location /api/ { access_log /var/log/nginx/api_access.log; proxy_pass http://127.0.0.1:8080/; ... }5.4 后端获取不到真实客户端IP症状后端日志里记录的客户端IP都是127.0.0.1或Nginx服务器的内网IP。解决确保在location /api/块中配置了proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;。然后你的后端应用需要从这些HTTP头中读取IP而不是直接从TCP连接获取。例如在Spring Boot中可以配置server.forward-headers-strategynative或使用RequestHeader(X-Real-IP)来获取。5.5 WebSocket连接失败症状页面控制台报WebSocket连接错误或连接建立后立刻断开。排查确认Nginx配置了正确的location块来处理WebSocket路径并且包含了Upgrade和Connection头。检查proxy_pass地址是否正确是否也需要移除路径前缀。适当增加proxy_read_timeout和proxy_send_timeout的值避免因为长时间没有数据交互而被Nginx超时断开。5.6 配置测试与调试命令汇总sudo nginx -t每次修改配置后必运行检查语法。sudo nginx -s reload 平滑重载配置不影响已建立的连接。sudo tail -f /var/log/nginx/error.log 实时查看错误日志定位问题根源。sudo tail -f /var/log/nginx/access.log 查看所有访问记录分析请求流向。curl -I http://your-server/api/health 从服务器本地测试API代理是否通畅。netstat -tlnp | grep :端口号 检查某个端口是否被监听及由哪个进程监听。最后我个人在多次部署中最大的体会是“先分后合逐步验证”。不要一次性把前后端和Nginx全配好。可以先让后端服务直接对外网IP的端口临时开放用Postman测试通。再单独配置Nginx静态托管前端确保页面能打开。最后再配API代理并用浏览器开发者工具一步步看网络请求。这样任何一步出错你都能快速定位到是前端、后端还是Nginx代理的问题。Nginx配置就像搭积木理解了每个指令的作用组合起来就能应对各种复杂的场景。