☰
前后端分离项目云服务器部署:Nginx反向代理与CentOS实战
2026/10/17 1:03:24 网站建设 项目流程

很多开发者第一次把自己做的前后端分离项目部署到云服务器上时,都会卡在同一个环节:本地跑得好好的,一上服务器就各种玄学问题。前端页面打不开、接口 502、刷新页面就 404、远程连不上服务器,每一条都能折腾掉一晚上。我前段时间重新梳理了一套在基于阿里云 CentOS 7.x 环境下搭建前后端分离 Web 服务器的完整流程,把整个部署链路从头到尾走了一遍,这中间踩了不少坑,也总结出很多容易忽略但非常关键的细节。

这篇文章不是只给一堆命令,而是把“为什么要这样做”“每一步解决了什么问题”讲清楚。适合第一次把项目部署到云服务器上的同学,也适合那些已经能跑通但总感觉部署得不够稳、出了问题不知道从哪排查的人。放心,整个思路换到其他云服务商、换到 Ubuntu 系统,同样适用。

1. 整体部署思路:先定端口规划和 Nginx 的角色定位

1.1 为什么前后端分离部署一定要引入 Nginx

很多新手会把前端打包出来的 dist 目录直接丢进后端的 static 静态目录,让后端框架顺带托管前端页面。这种方式在本地开发时看着没问题,但一旦上了服务器,分离的意义就没了:前端想独立更新必须重新打后端包,后端一重启前端也跟着断,静态文件的传输效率也远不如专业服务器。

正确的架构应该是:Nginx 作为唯一的对外入口,承担两件事。第一,托管前端构建出来的静态文件,用户访问域名时直接由 Nginx 返回 HTML、JS、CSS。第二,反向代理后端接口,凡是/api开头的请求,Nginx 转发给内网里的后端服务。前端页面和后端服务彼此独立,互不干扰,后续扩展、更新、加负载均衡都非常方便。

1.2 动手之前先把端口规划写明白

我见过太多人上了服务器就开始装环境,结果后面全乱套。这里强烈建议先画一张端口规划表,把所有服务绑定到哪个 IP、哪个端口列清楚,贴在旁边再动手。

服务监听地址端口对外暴露说明
Nginx0.0.0.080/443是唯一对外入口,响应 HTTP/HTTPS
SSH0.0.0.022建议限制来源 IP远程管理,不对外开最好
后端接口服务127.0.0.18080否只让本机 Nginx 访问
数据库127.0.0.13306否同样只绑内网地址

目录规划也建议固定下来,我的习惯是前端静态文件放/var/www/html,后端应用代码放/opt/app,日志统一进 systemd 的 journal。这种目录结构的好处是一眼就能看出每块东西在哪,排查问题不用到处找。

1.3 整个部署流的顺序安排

步骤顺序也有讲究。先初始化服务器环境,再把后端服务跑起来并验证接口,最后部署前端静态资源并配 Nginx。顺序反了会很难受:先配好前端发现后端接口没起来,又要回头折腾,排查问题时分不清是 Nginx 的问题还是后端的问题。按照“底层往上搭、先内后外”的顺序,每一步的验证范围都很清晰,出错时能快速锁定位置。

2. 服务器初始化:安全组、系统环境与基础工具

2.1 第一道坑:安全组放行规则

在云服务器控制台购买实例之后,第一件要做的事是配置安全组规则。很多人刚买完服务器就拿着 IP 开始 SSH 连接,结果超时连不上,第一反应是系统出问题了,其实大概率是安全组没放行 22 端口。

以阿里云为例,找到一个叫“安全组”的入口,点进去配置规则,方向选择“入方向”,添加入方向规则。HTTP 的 80 端口、HTTPS 的 443 端口直接放行0.0.0.0/0,SSH 的 22 端口我建议只放行你自己当前网络的公网 IP,精确到/32,而不是完全暴露给全网。这样即使服务器密码不够复杂,也能挡掉绝大部分自动扫描攻击。

还有一个很容易忽略的地方:云服务商的安全组和 CentOS 系统自带的 firewalld 防火墙是两层独立的机制。安全组放行了 80 端口,如果系统防火墙没有放行,外部依然无法访问。后面每一步操作,心里都要有这个两层概念。

2.2 系统基础环境的铺设

拿到一台全新的 CentOS 7.x 实例后,先做系统更新和基础工具安装,一条命令搞定:

yum update -y yum install -y vim wget curl tar net-tools lsof unzip

net-tools提供netstat命令,lsof用来查看端口占用情况,这两工具排查网络问题的时候几乎是必备的。

CentOS 7 自带的时间同步服务是 chrony,检查一下有没有正常运行:

systemctl status chronyd timedatectl

如果时区不是 Asia/Shanghai,可以顺手改掉,防止后端服务打日志的时间和你的直觉对不上。还有 SELinux 的问题,CentOS 7 默认开启 SELinux,会让很多操作莫名其妙被拒绝。我不建议一上来就永久关闭,但如果你调试时发现权限明明没问题却一直报错,可以先用getenforce查看状态,然后临时切到宽松模式试一下:setenforce 0。确认是它的原因之后,再决定是调整文件上下文还是按需配置。

2.3 firewalld 放行端口

CentOS 7 默认装了 firewalld,很多教程会让新手直接把它停掉,理由是“反正云服务商有安全组”。这个做法不能说错,但会让系统少一层防护。我更倾向的做法是保留 firewalld,只放行需要的端口:

firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https firewall-cmd --permanent --add-port=22/tcp firewall-cmd --reload

注意安全组和 firewalld 是“与”的关系,两边都要放行了才能通。这也是后面排查端口不通时的第一个必查项。

3. 前端静态资源部署:构建、传输与 Nginx 托管

3.1 构建前必须处理的环境变量问题

前端项目在本地开发时,接口地址常常直接写死成http://localhost:8080,这些配置在生产环境必须替换。现在主流的前端框架都支持通过环境变量注入配置,比如 Vite 项目里用import.meta.env.VITE_API_BASE,在项目根目录创建.env.production文件:

VITE_API_BASE=/api

然后在封装请求工具时统一拼接这个前缀:

// axios 或 fetch 的 baseURL 统一使用环境变量 const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE || '/api', timeout: 10000 })

这样构建出来的前端代码只会请求同源的/api/xxx,浏览器不会产生跨域问题,同时也给后面 Nginx 反向代理留了一个清晰的路由规则:所有/api开头交给后端。这个“统一前缀”的约定是整个前后端分离部署架构里最关键的一步,一定要在写代码阶段就做好,不要等项目开发完了再去改。

3.2 路由模式决定 Nginx 必须做的特殊处理

绝大多数 Vue/React 项目的路由用的是 History 模式,也就是createWebHistory或BrowserRouter。这种模式请求的 URL 看起来是正常的路径,比如/user/profile,但服务器上根本没有对应的物理文件,刷新页面时 Nginx 会先尝试找这个路径的文件,找不到就返回 404。这就是经典问题:首页能打开,刷新就白屏。

解决办法是 Nginx 配置里做一次回退,把所有找不到的路径都引导回index.html,让前端路由接管。后面配置 Nginx 时专门说,这里先记住:History 模式必须配try_files $uri $uri/ /index.html;。如果你用的是 Hash 模式,URL 里带#,就没有这个问题,但看起来不够专业,还是建议前端换 History 模式。

3.3 构建产物传输:rsync 是最佳实践

本地执行构建:

npm run build

这一步会生成dist目录,里面是纯静态文件。接下来把产物传到服务器上。传统做法是scp -r dist/* root@服务器IP:/var/www/html/,简单是简单,但每次更新都要传全部文件,项目大了之后很浪费时间。

我推荐用 rsync,它只传输有变化的部分,而且边传边校验,速度和安全都有保障:

rsync -avz --delete dist/ root@服务器IP:/var/www/html/

--delete参数会把服务器上多余的文件删掉,确保远端和本地完全一致,避免旧文件残留导致奇怪的问题。这条命令值得做成脚本,以后每次发布只改一下本地路径就行。

3.4 Nginx 静态资源托管配置

创建 Nginx 配置文件/etc/nginx/conf.d/web.conf,先把静态托管部分写上:

server { listen 80; server_name your-domain.com; root /var/www/html; index index.html; location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 30d; add_header Cache-Control "public, no-transform"; } }

try_files那一行的含义是按顺序尝试:第一个参数$uri,也就是请求的完整路径对应的文件,例如/about就找/var/www/html/about,找不到就再试第二个参数$uri/,找目录下的默认文件,还找不到就统一返回/index.html。这样前端路由的 History 模式就能正常工作,无论你跳到哪个子路径,刷新后都会回到前端框架手里。

静态资源加 30 天缓存能显著提升加载速度,但注意只对带文件扩展名的资源生效,HTML 页面本身千万别缓存。

写完配置后先检查语法再重载:

nginx -t systemctl reload nginx

到这里,直接用 IP 访问服务器,应该能看到前端页面了。

4. 后端接口服务:部署、内网绑定与 systemd 守护

4.1 部署后端服务前确认三件事

后端项目五花八门,Node.js、Java Spring Boot、Python Django,具体命令不同,但上服务器之前必须确认三件事。

第一,运行环境是否齐全。Node.js 项目要装对应版本的 Node,Java 项目要装 JDK,Python 项目要建虚拟环境装依赖。这一步在 CentOS 7 上有个隐含的坑:默认 yum 源里的版本往往很老,建议直接去官方渠道安装。

第二,数据库连接信息。开发环境用的通常是本机数据库,生产环境要改成服务器上的数据库地址。不要把生产数据库密码硬编码在源码里,放在.env环境变量文件里,并确保这个文件在 git 忽略列表中。

第三,后端服务监听哪个地址和端口。我画过端口规划表,这里对应的是 127.0.0.1 的 8080 端口。监听内网地址能从根本上避免后端服务暴露到公网。比如一个常见的 Node.js 服务在入口文件里:

app.listen(8080, '127.0.0.1', () => { console.log('API server running at http://127.0.0.1:8080') })

如果后端用了0.0.0.0,意味着整个公网都能直接访问你的 8080 端口,即使安全组没放行,也存在被扫描探测的风险。所以后端服务一律绑 127.0.0.1,只让本机的 Nginx 走内部网络转发。

4.2 用 systemd 把后端服务变成常驻进程

很多初学者的后端服务是用nohup node app.js &这种方式在后台跑着的,弊端很明显:服务器一重启服务就没了,进程崩溃了也不会自动恢复,想找日志还要自己维护文件。我强烈建议用 systemd 管起来,这是 CentOS 7 自带的进程管理方案。

新建一个 service 文件,例如/etc/systemd/system/myapp.service:

[Unit] Description=My API Server After=network.target [Service] Type=simple WorkingDirectory=/opt/app EnvironmentFile=/opt/app/.env ExecStart=/usr/bin/node /opt/app/server.js Restart=always RestartSec=3 User=www [Install] WantedBy=multi-user.target

关键参数说一下:

  • Restart=always只要进程异常退出就自动拉起,崩溃恢复不用人工干预
  • EnvironmentFile指定环境变量文件,生产数据库密码等敏感信息都放这里
  • User=www指定运行用户,不要用 root 跑业务服务,降低风险
  • RestartSec=3崩溃后等 3 秒再重启,避免快速循环重启把系统打满

然后执行:

systemctl daemon-reload systemctl enable --now myapp systemctl status myapp

enable是开机自启,--now是同时启动。From now on,只要服务器开机,这个服务就在。需要重启服务时systemctl restart myapp,想看日志时journalctl -u myapp -f,一切都变得非常干净。

4.3 部署完必须先在本机验证接口

配好 systemd 之后,不要急着去配 Nginx,先在服务器本机验证后端服务是不是真的通了:

curl http://127.0.0.1:8080/api/health

能看到 JSON 响应数据,说明服务本身没问题。这一步如果都没通过,问题就出在后端环境、依赖安装、数据库连接这三个环节。我是建议把健康检查接口作为后端服务的标配,就一个最简单的接口返回{ "status": "ok" },后面排查问题时省太多事了。

4.4 为什么我不推荐宝塔面板

现在很多教程推荐直接装宝塔面板来一键部署,确实便利。但我个人建议至少第一次部署时走一遍手动流程。原因很简单:宝塔把太多细节封装掉了,一旦出问题,比如反向代理配置错误、防火墙规则冲突,你根本不知道它生成了什么配置、改了什么文件,排查起来会比手动部署难得多。手动部署一次,你对整条链路有了完整的认知,后面再遇到问题就能靠自己的判断解决,而不是永远依赖“点一下”。

5. 反向代理打通 API 通路:配置细节与跨域问题的消失

5.1 核心配置:带不带斜杠的 proxy_pass 天差地别

前端静态资源部署好之后,页面上调接口大概率会报错。打开浏览器控制台,看到的往往是某个/api/xxx请求返回 404 或者 502。这时需要在 Nginx 配置里加上反向代理这一段。

回到刚才的/etc/nginx/conf.d/web.conf,在 server 块里加一个 location:

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_pass结尾到底带不带斜杠。proxy_pass http://127.0.0.1:8080;这种写法,会保留原始的完整 URI 转发给后端。前端请求/api/user/list,后端收到的是/api/user/list。第二种写法,proxy_pass http://127.0.0.1:8080/;结尾多了一个斜杠,会把 location 匹配到的前缀/api替换掉,后端收到的就变成/user/list。

后端接口本身是按什么路径写的,决定配置该怎么选。我后端代码里接口路径带/api前缀,就用不带斜杠的写法;如果后端接口路径本身不带/api,就要用带斜杠的写法。很多人在这一步反复折腾,就是因为没搞懂这条规则。建议在配置之前先去后端代码里看一眼路由前缀,比遇到问题后瞎猜高效得多。

5.2 反向代理让跨域问题彻底消失

单独跑一个前端静态服务、一个后端接口服务,浏览器直接从前端域名去请求后端域名时,会产生经典的 CORS 跨域问题。以前写代码的人可能第一反应是给后端加 CORS 插件,开放跨域请求。但在“打包静态文件 + Nginx 反向代理”的架构下,跨域问题天然不存在了。

原因很简单:浏览器只看到一个域名,假设是your-domain.com。页面从your-domain.com/加载,数据请求也发给your-domain.com/api/xxx,两个地址同源,浏览器根本不会触发 CORS 检查。前端代码不需要处理跨域,后端也不需要加 CORS 头,请求头里的 Origin 始终是同一个域名。这也是前后端分离部署相对“后端托管前端”最大的好处之一。

5.3 特殊场景:WebSocket 和上传文件大小

如果你的项目有用到 WebSocket,比如实时聊天、消息推送,在 Nginx 代理时需要额外添加升级请求头的处理:

location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; }

没有这些配置,WebSocket 连接建立后很快就会被 Nginx 掐断,表现为客户端反复重连。

另外,上传文件功能如果经常报 413 错误,是因为 Nginx 默认只允许 1MB 的请求体大小。在 server 块里加上一行:

client_max_body_size 100m;

这个值按业务实际需要调整,一般 100MB 够大部分场景用了。

配置改完之后,先nginx -t确认语法正确,再systemctl reload nginx让配置生效。

6. 上线后的验证流程与高频故障排查

6.1 一套完整的验证流程

很多人的“验证”就是打开浏览器看一眼,能打开就说部署成功,这远远不够。我习惯按以下顺序完整过一遍:

# 第一步:验证 Nginx 配置语法 nginx -t # 第二步:服务器本机验证前端页面 curl -I http://127.0.0.1 # 第三步:服务器本机验证后端健康检查接口 curl http://127.0.0.1:8080/api/health # 第四步:通过 Nginx 代理验证接口 curl http://127.0.0.1/api/health

四步全部通过之后,再用浏览器访问公网 IP 或者域名,走一遍核心业务流程:登录、查列表、提交数据、退出登录。这一步不能省,因为安全组、防火墙、DNS 解析、HTTPS 证书等一系列外部因素,只有通过公网请求才能真正验证到。

6.2 高频故障的定位思路

部署完成之后总会有各种意想不到的问题,我把高频故障和排查链路整理成了一张表,按这个顺序查,基本能覆盖 90% 的情况:

现象第一时间查什么常见原因
SSH 连不上服务器云控制台安全组规则22 端口未放行
浏览器打不开页面安全组和 firewalld 的 80/443两层防火墙漏放行
页面打开但接口 502后端服务状态systemctl status myapp服务没起来或端口不对
页面打开但接口 404Nginx error log + 后端接口路径proxy_pass 斜杠问题
刷新子页面变 404Nginx 配置里的 try_filesHistory 路由回退没配置
接口响应 504后端是否耗时过长增加 proxy_read_timeout
上传文件报 413Nginx 请求体限制加 client_max_body_size

排查时永远记住一个原则:先分清问题出在前半段还是后半段。服务器上执行curl http://127.0.0.1,如果返回了 HTML,说明 Nginx 静态服务正常,问题在前端构建产物或路由配置上。再执行curl http://127.0.0.1:8080/api/health,如果通,说明后端服务正常,问题在 Nginx 反向代理的配置细节上。一旦通过服务器本机把问题范围压缩到“Nginx 这一个环节”,剩下的就是对照配置文件一行一行检查的事。

6.3 Nginx 日志是最好的排错老师

遇到问题不要靠猜,直接看日志。Nginx 的错误日志默认在/var/log/nginx/error.log,动态跟踪:

tail -f /var/log/nginx/error.log

后端服务的日志通过 journalctl 看:

journalctl -u myapp -f

有一次我遇到接口偶发 502,排查了半天,最后在 Nginx 错误日志里看到一行upstream prematurely closed connection,顺着这个线索才发现是后端进程因为内存不足被系统杀了,而 systemd 正在反复拉起它。如果没有日志,这个问题会浪费更多时间。

6.4 几条压箱底的老经验

改 Nginx 配置之前,先备份原文件;改完之后测试环境先验证,生产环境尽量选在流量低的时间段操作。这条规矩救过我很多次。

后端服务的日志一定要开,无论是请求日志还是错误日志。很多开发者本地方便,生产环境为了省事不开日志,出问题的时候两眼一抹黑。从部署的第一天就养成看日志的习惯,后面运维会轻松很多。

最后,CentOS 7 已经进入维护末期,新项目建议直接考虑更新的系统版本,但部署思路和 Nginx 配置是完全通用的。如果你已经跑在 7.x 上,也建议尽早把迁移列入计划,同时做好数据备份,别等到出问题才动手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询