前阵子帮朋友把一个前后端分离的个人项目推到公网服务器上,整个过程走下来,最大的感受就是:用宝塔面板做可视化部署,确实比纯 SSH 命令行操作省心太多。特别是对于 Spring Boot 3 后端加 Vue 3 前端这种典型组合,从 Ubuntu 安装宝塔面板,到配置 Nginx、MySQL、Redis,再到把前后端分别发布到公网,每一个环节都能在浏览器里点出来,不用再对着黑窗口一遍遍敲命令、改配置、试错。这篇文章就把我这次完整的部署过程整理出来,从环境准备到最终上线,每一步的选型思路、配置文件、踩坑记录都写清楚,刚接触服务器部署的新手可以照着做,有经验的开发者也能从中找到一些平时容易忽略的细节。
1. 为什么选择宝塔面板做可视化部署
1.1 可视化部署到底解决了什么问题
先聊点实在的。没有面板之前,部署一个前后端分离项目需要手动干这些事情:安装 Nginx、配置站点、修改 nginx.conf;安装 MySQL,初始化数据库、创建账号授权;装 Redis,设置密码、绑定内网地址;还要装 Java 环境、用 nohup 把 jar 包拉起来,再用 grep 去查日志。这套流程写下来就够累的,实际执行的时候,任何一个配置项写错,排查起来都是按小时计的。
宝塔面板的核心价值,就是把这些重复劳动变成可视化操作。软件商店里点两下,Nginx、MySQL、Redis 就装好了;数据库管理页面直接创建库、创建用户;网站页面填写域名和根目录,Nginx 配置文件就自动生成好了;SSL 证书也能在面板里一键申请。对个人开发和中小团队来说,这不仅仅是省时间,更重要的是降低了部署门槛——你不需要记住 Nginx 每一行配置的含义,也不需要手写 MySQL 的 GRANT 授权语句,面板已经把最常见的场景封装好了。
我个人用过挺长时间命令行部署,也经历过线上环境改错配置导致整站 502 的深夜。改用宝塔之后,至少大部分操作都能通过面板来管理,出问题也方便回溯。这里要说清楚,宝塔不是让你完全不懂底层原理,而是让你把精力集中在业务代码本身,把那些枯燥的环境搭建交给可视化界面。
1.2 部署方案的整体架构
这次要部署的是 Spring Boot 3 + Vue 3 的项目,整体架构比较典型,先在一张表里理清楚:
| 层级 | 组件 | 服务器上的角色 | 对外暴露端口 |
|---|---|---|---|
| 入口 | Nginx | 托管前端静态文件、反向代理后端接口 | 80 / 443 |
| 前端 | Vue 3 构建产物 | dist 目录,由 Nginx 直接托管 | 无独立端口 |
| 后端 | Spring Boot 3 | 可执行 jar 包,Java 进程运行 | 8080(仅内网) |
| 数据库 | MySQL | 业务数据存储 | 3306(仅内网) |
| 缓存 | Redis | 会话、验证码、热门数据缓存 | 6379(仅内网) |
浏览器访问用户域名时,请求先到 Nginx:如果请求的是/开头的静态资源路径,Nginx 直接把 Vue 构建后的 dist 目录里的文件返回给浏览器;如果请求的是/api开头的接口,Nginx 将请求反向代理给内网 8080 端口的 Spring Boot 服务。
这样设计有几个明显的好处。第一,Nginx 对静态文件处理效率很高,前端资源由它托管能扛住不少并发。第二,浏览器只跟域名打交道,前端和后端在同一个域名下,天然避免了跨域问题,不用在前端代码里写一堆 CORS 配置。第三,MySQL、Redis、后端 Java 进程都不直接暴露公网,只监听内网地址,攻击面小很多。这也就是“公网部署”和“内网开发环境”最大的区别——公网环境必须考虑端口暴露、进程守护、日志管理这些事。
2. Ubuntu 服务器环境准备与宝塔面板安装
2.1 服务器选型与系统初始化要点
部署的第一步是准备一台公网服务器。系统方面我推荐 Ubuntu 22.04 LTS,宝塔对 Ubuntu 的支持很完善,而且 22.04 是 LTS 版本,软件源和内核维护期都很长。配置方面,单纯跑一个 Spring Boot 项目加 Nginx,1 核 2G 内存就够用了;如果打算上面同时跑 MySQL 和 Redis,内存建议选 2G 以上,避免内存不足触发 OOM 把进程杀掉。
拿到服务器后,我建议先做系统更新,把系统自带的软件包升到最新版本:
apt update && apt upgrade -y这一步不是可选的。刚买来的服务器,系统镜像的软件包版本往往比较旧,先更新可以减少后续安装软件时遇到依赖冲突的概率。然后再装一下基础工具,比如 curl、vim、lsof 这些,后面排查问题都会用到:
apt install -y curl vim lsof net-tools服务器联网后,还要确认几个信息:系统架构(一般用uname -m,x86_64 是最常见的)、磁盘分区情况(df -h)、内存大小(free -h)。这些信息决定后面装什么版本的软件、JVM 内存参数怎么调。比如 2G 内存的机器,Spring Boot 的 JVM 堆内存就控制在 512M 左右比较稳妥,不能默认分配很大。
提示:如果服务器是云厂商的轻量应用服务器,记得在控制台安全组里提前放行 80、443 端口,以及后面面板自定义的端口。安全组没放行,服务器里再怎么配置都访问不了。
2.2 宝塔面板安装与首次安全设置
Ubuntu 上安装宝塔面板,官方给了一键安装脚本。执行这个脚本,面板会自动拉起一个 Web 服务,托管在 8888 端口:
curl -sSfL https://download.bt.cn/install/install-ubuntu_6.0.sh | sudo bash安装过程中会有一段时间在下载和编译组件,大概几分钟到十几分钟不等,取决于服务器网络状况。装完后,终端会输出面板的访问地址、默认用户名和密码,类似下面的格式:
- 面板地址:
http://服务器公网IP:8888/xxxxxxx - username:
xxxx - password:
xxxxx
这里的/xxxxxxx是面板的随机安全入口,一定要保存好。我见过很多新手直接访问http://IP:8888,结果发现页面打不开,就是因为安全入口这段路径没有带上。
第一次访问面板页面,会要求绑定宝塔账号。这个是免费的,去宝塔官网注册一个账号绑定即可,目的是面板更新和部分功能需要账号体系支撑,不影响正常使用。
登录之后,先别急着装环境,做三件安全设置:
- 修改面板端口:把默认的 8888 改成其他端口,比如 8899。默认端口是扫描器最常探测的端口,改掉能省掉不少恶意请求。
- 设置安全入口:把面板登录地址的随机路径换成一个自己记得住但别人猜不到的字符串,例如
/myadmin_bt_2024。 - 启动 BasicAuth 认证(可选):在“安全”页面开启,访问面板前会额外弹一层账号密码认证。
这三个设置做完,面板对外暴露的入口才算安全一些。注意这些安全措施都是面板自带的功能,属于常规服务器维护操作,强烈建议养成习惯。
3. 面板环境安装:LNMP 环境配置
3.1 软件商店安装 Nginx、MySQL、Redis
宝塔登录成功后,进入“软件商店”页面,在“运行环境”分类里找到 Nginx、MySQL、Redis。这里不用去服务器上执行 apt 安装,直接点击“安装”就行。安装前有几个选型细节:
- Nginx:选稳定版 1.22 或 1.24,不建议选 mainline 版本。稳定版在模块兼容性和稳定性上更可靠,个人项目完全够用。
- MySQL:机器内存小于 2G 推荐 MySQL 5.7,内存充足就上 MySQL 8.0。8.0 的默认认证插件是 caching_sha2_password,一些老版本 JDBC 驱动可能不兼容,但 Spring Boot 3 使用 MySQL 8.0 驱动没有这个问题。
- Redis:选 7.x 版本,安装后默认监听在 127.0.0.1,这个不需要改。
面板里有“极速安装”和“编译安装”两种方式。新手或者追求省时间的,直接选极速安装,它使用系统软件包安装,速度快;编译安装可以自定义参数,但耗时长,不建议在没有特殊需求时使用。我在这次部署中选的是极速安装,前后不超过十分钟,三个软件就全部装好了。
3.2 数据库与 Redis 初始化与访问控制
软件装完之后,进入“数据库”页面,点击“添加数据库”。宝塔会自动生成数据库名、用户名和一个随机密码,你也可以自己指定。这里我建议自己设定一个复杂度足够的密码,至少要包含大小写字母、数字和特殊字符。创建完成后,宝塔会把这个库的权限自动授权给对应账号,不用手动执行 GRANT 语句。
如果你需要图形化管理数据库,宝塔自带的 PHPMyAdmin 或者 Adminer 足够用了。在“数据库”页面点击“管理”按钮,就会通过面板配置的路径打开数据库管理界面。生产环境中,PHPMyAdmin 默认路径是随机的,不对外直接开放,这个设计比较稳妥。
Redis 方面,宝塔安装完默认没有设置密码,这是公网部署的大忌。Redis 如果没有密码校验,又监听了公网地址,黑客可以直接连上去写 cron 计划任务,实现远控。必须立刻设置密码。
Redis 配置文件一般在/www/server/redis/redis.conf,在里面找到requirepass这行,改成:
requirepass 你的强密码改完后在宝塔软件商店里找到 Redis,点击“重启”让配置生效。后端 Spring Boot 配置文件中,只要在spring.redis.password填入同一个密码即可。实际项目中还可以进一步配置 Redis 只监听 127.0.0.1,这是默认配置,我就不额外改了。
4. Spring Boot 3 后端部署实操
4.1 本地打包:Maven 构建可执行 jar
后端代码在本地开发测试通过后,需要打包成可执行的 jar 包。Spring Boot 3 要求 Java 17 及以上,所以本地 JDK 版本必须是 17+,否则打包时会报编译错误。进入项目根目录,执行:
mvn clean package -DskipTests-DskipTests表示跳过单元测试,加快打包速度。如果你希望测试跑完再打包,去掉这个参数就行,但生产构建一般都会跳过,避免测试环境配置导致打包失败。
打包完成后,target 目录下会生成一个xxx.jar文件。这里要注意 Spring Boot 的 Maven 插件是否配置了repackage目标,正常情况下 Spring Boot 项目都会自动生成可执行 jar,体积比普通 jar 大不少,里面包含了所有依赖。如果生成的文件很小(几十 KB),多半是打成了普通 jar,Spring Boot 插件配置有问题,需要检查spring-boot-maven-plugin。
4.2 上传 jar 包与 Java 环境检查
jar 包传到服务器这一步,用宝塔自带的文件管理器最方便。打开“文件”页面,进入/www/wwwroot/目录,为项目建一个独立目录,比如/www/wwwroot/myapp/,然后把 jar 包拖拽进去。相比 scp 命令,面板文件管理器对不熟悉终端的人友好很多,而且支持断点续传。
上传完后,检查服务器 Java 环境。宝塔不会自动装 Java,需要在终端执行:
apt install -y openjdk-17-jdk java -versionjava -version输出里看到 OpenJDK 17,说明环境就绪。如果服务器上已经有多版本 Java,需要确认java命令指向的是 17,可以在终端用update-alternatives --config java切换。
在正式交给进程守护工具之前,我习惯先在终端手动启动一次 jar,验证环境和配置对不对:
cd /www/wwwroot/myapp && java -jar xxx.jar --spring.profiles.active=prod看到Started相关的日志,说明后端启动成功。这时可以用curl http://127.0.0.1:8080/api/hello测一下接口通不通。能通再关掉进程,进入下一步。
4.3 用 Supervisor 守护后端进程
直接java -jar启动的后端进程,有一个很大的问题:终端一关,进程就可能收到 SIGHUP 信号退出;进程崩溃了也没人自动拉起来;日志输出到终端,不方便动态查看。生产环境必须有一个守护进程来管理它。
宝塔软件商店里有一个“进程守护管理器”(Supervisor),就是干这个用的。安装后,进入“进程守护”页面,添加守护进程:
- 名称:比如
myapp-backend - 启动用户:
root或者 www 用户,注意文件权限即可 - 运行目录:
/www/wwwroot/myapp - 启动命令:
java -jar /www/wwwroot/myapp/xxx.jar --spring.profiles.active=prod - 进程数量:
1
这里提个细节,如果后端配置文件里的数据库地址、Redis 地址是通过环境变量注入的,可以把环境变量填在“环境变量”栏里,格式是KEY=VALUE,每行一个。Supervisor 启动时会自动加载这些变量,避免在 jar 包和启动命令里暴露明文密码。
配置完成后,点击“启动”。Supervisor 会把进程挂在自己的名下,后端崩溃会自动拉起,服务器重启后也会自动启动。日志可以在面板页面上直接查看,也可以配置日志文件位置,路径填为/www/wwwroot/myapp/logs/out.log,方便后面排查问题。
我在实际使用中遇到过一个坑:jar 包所在目录是一个挂载的磁盘分区,如果开机挂载时机晚于 Supervisor 服务启动,目录还是空的,Supervisor 会找不到 java 命令。解决办法是给 Supervisor 服务加一个等待,或者把 jar 放到系统盘路径下。这个问题不是每次都会遇到,但碰到了确实容易一头雾水。
5. Vue 3 前端构建与 Nginx 发布
5.1 前端环境准备与生产构建
前端部分需要在本地执行构建,需要 Node.js 18 及以上版本。进入前端项目目录:
npm install npm run buildnpm run build执行完成后,项目根目录下会生成dist/目录,里面就是打包好的 HTML、JS、CSS 静态文件。构建之前最关键的一步,是确认生产环境接口地址。
Vue 3 + Vite 项目通常在根目录有个.env.production文件,里面配置了生产环境的环境变量。这里有两种风格:
VITE_API_BASE_URL=/api # 或者 VITE_API_BASE_URL=https://your-domain.com/api我推荐用相对路径/api。原因很简单:前端和后端部署在同一个域名下,Nginx 会把/api开头的请求代理到后端,前端代码里用/api作为前缀,就不存在写死 IP 或域名导致环境切换的问题。npm run build时,Vite 会把代码里的import.meta.env.VITE_API_BASE_URL替换为实际值,打包进 JS 文件,所以构建前一定要确认这个变量是对的。
5.2 上传 dist 目录到服务器目录
回到宝塔面板,在文件管理器中进入站点根目录。我习惯在/www/wwwroot/下新建一个专门放前端资源的目录,例如/www/wwwroot/myapp-web。把本地 dist 目录里的内容全部上传到这个目录。
很多新手容易犯一个错误:把 dist 目录整个压缩包上传,然后传到服务器上忘了解压,Nginx root 指到.zip文件上当然访问不了。我用文件管理器一般是先上传 zip 压缩包,在面板里选中压缩包直接点“解压”,解压完确认目录结构正确——也就是 index.html 直接在站点根目录下,不是dist/dist/index.html这种嵌套结构。
5.3 Nginx 站点配置与 SPA 路由适配
前端文件上传后,进入宝塔“网站”页面,点击“添加站点”,填写要绑定的域名(例如www.example.com),根目录选择/www/wwwroot/myapp-web。宝塔会自动生成一份 Nginx 配置,这份配置默认支持静态站点访问,但 Vue 3 默认使用 HTML5 History 模式路由,如果不做处理,刷新某个子路由(比如/about)时,Nginx 找不到对应路径的物理文件,会返回 404。
解决办法是在站点配置文件中加入一段 try_files 指令。进入“网站” -> 设置 -> 配置文件,把默认的location /配置调整为:
location / { root /www/wwwroot/myapp-web; index index.html; try_files $uri $uri/ /index.html; }try_files的含义是:如果当前请求路径能对应到真实文件,就直接返回;如果找不到,就回退到 index.html,由 Vue 路由接管页面。这样刷新子路由就不会 404 了。
另外建议开启 gzip 压缩,减少静态资源传输体积:
gzip on; gzip_min_length 1k; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;保存配置后,在网站页面点“重载配置”,前端静态站点基本就生效了。这时候访问http://域名应该能看到前端页面,但接口还是不通,下一步就处理反向代理和 HTTPS。
6. 域名解析与 HTTPS 配置
6.1 域名解析到服务器 IP
前端页面要在公网被访问,必须让域名指向服务器 IP。这一步在云厂商的域名解析控制台操作:添加一条 A 记录,主机记录填www或者@,记录值填服务器公网 IP。
DNS 解析生效需要一定时间,一般是几分钟到几十分钟,取决于 TTL 设置。可以用ping 你的域名来验证是否解析到目标 IP。如果想同时支持www.example.com和example.com,就添加两条 A 记录,都指向同一个 IP,然后在 Nginx 站点配置里绑定这两个域名。
6.2 配置 Nginx 反向代理
前端页面已经能访问了,但页面里发起的/api请求目前还是 404,因为 Nginx 并不知道这个路径应该交给谁。现在需要在同一个站点配置里加入反向代理:
location /api/ { proxy_pass http://127.0.0.1:8080/api/; 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; }这段配置的作用是:把浏览器发来的/api/xxx请求,转发给本机 8080 端口的 Spring Boot 服务。配置里有几个细节值得注意:
第一,proxy_pass后面的 URL 末尾是否带/,行为完全不同。proxy_pass http://127.0.0.1:8080/api/;是保留完整路径,也就是/api/xxx原样转发;如果写成proxy_pass http://127.0.0.1:8080;不带路径,则是去掉/api前缀后转发,实际传给后端的是/xxx。这个区别很容易踩坑,后端的 Controller 路径一定要和转发后的路径匹配,否则接口全 404。
第二,proxy_set_header这几个头很重要。Host头如果不设置,后端收到的请求 Host 是127.0.0.1:8080,某些依赖 Host 的代码(比如生成链接)就会错。X-Real-IP和X-Forwarded-For让后端拿到客户端的真实 IP,对于记录请求日志、风控都有价值。
再加上一步:如果后端有个别接口包含文件上传,需要调大 request body 限制,否则 Nginx 默认只能接收 1M 请求体,上传大文件会直接报413 Request Entity Too Large。可以在 server 块里加一行:
client_max_body_size 50m;这个值根据实际业务调整,上传图片、文件的应用建议至少设为 20m 以上。
6.3 申请 SSL 证书并开启强制 HTTPS
域名解析生效后,就可以申请 HTTPS 证书了。宝塔的“网站” -> SSL 页面,选择 Let's Encrypt 免费证书,勾选绑定的域名,点击申请。面板会自动完成域名校验,一般一分钟内就签发成功。到期前,宝塔会在证书过期前自动续签,不需要人工干预。
申请成功后,打开“强制 HTTPS”开关。这样所有通过 http 访问的请求都会 301 跳转到 https,确保整个站点走加密通道。配置里自动生成的跳转规则长这样:
if ($server_port !~ 443){ rewrite ^(/.*)$ https://$host$1 permanent; }也可以直接在站点配置里添加listen 443 ssl;并加载证书文件。这些操作面板都自动处理了,重点是在浏览器里实际访问一下https://你的域名,确认地址栏有小锁标志,接口也能正常请求。
至此,一个公网可访问的 Spring Boot 3 + Vue 3 项目就算正式上线了。不过上线只是开始,后面的维护和问题排查才是长期工作。
7. 常见问题与排查技巧实录
7.1 后端启动失败或访问出现 502
后端是整套系统的核心,启动失败出现的概率也最高。我整理了最典型的几个现象和对应的排查方向:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 启动日志显示端口被占用 | 8080 端口被其他进程占用 | lsof -i:8080找到占用的进程,kill 或改启动端口 |
启动报UnsupportedClassVersionError | Java 版本低于 17 | java -version检查版本,切换到 OpenJDK 17 |
| 启动报数据库连接失败 | MySQL 密码错误或账号没有权限 | 检查application-prod.yml中的数据库地址、用户名、密码 |
Redis 连接报NOAUTH Authentication required | Redis 已设置密码但后端没配置 | 检查spring.redis.password是否与 Redis 配置一致 |
| Nginx 访问接口返回 502 | 后端进程没有启动,或代理地址写错 | 先curl http://127.0.0.1:8080/api/xxx直连测通,再看 Nginx 代理配置 |
这里有个很实用的排查思路:先绕过 Nginx,直接在当前服务器上用 curl 访问后端接口,确认接口本身通不通。如果直连通、走域名不通,问题在 Nginx 配置;如果直连不通,问题在后端应用或中间件。这个二分法能快速缩小故障范围,不用在日志里大海捞针。
7.2 前端白屏或路由刷新 404
前端部署后最常见的问题是白屏。打开浏览器开发者工具(F12)的 Network 面板,刷新页面,看请求状态:
- 首页能打开,页面空白,Console 报错:多半是 JS 文件 404 或加载失败。检查站点根目录里是否真的有 js/css 文件,以及 Nginx 的 root 路径是否指对了。
- 首次打开正常,刷新子路由 404:这就是前面提到的 History 路由问题。确认 Nginx 配置里有没有加
try_files $uri $uri/ /index.html;。 - 页面能打开但接口请求地址不对:浏览器 Network 里看接口请求 URL,如果请求的是
localhost或错误的 IP,说明.env.production里的VITE_API_BASE_URL没配对,需要重新构建再上传。
还有一种情况比较隐蔽:前端项目使用的是vue-router的createWebHistory(),如果部署环境的服务端没有正确返回 index.html,刷新任何非根路径都会 404。上面 try_files 方案已经覆盖,这里不多说。
7.3 API 请求 401 或连接拒绝
前后端用 Nginx 反代后,大多数跨域问题已经消失。但有些情况下 API 还是会报 401:
前端请求里带了Authorization: Bearer <token>头,Nginx 默认会透传这个头到后端,一般不需要额外配置。但如果你在前端有自定义 header(比如X-Tenant-ID),Nginx 默认可能不转发,需要在反代配置里显式声明:
proxy_set_header Authorization $http_authorization; proxy_set_header X-Tenant-ID $http_x_tenant_id;如果后端报 401 不是因为头部缺失,那就是 Token 校验失败。Spring Boot 3 + Spring Security 的项目里,检查一下 Security 配置里放行了哪些路径。生产环境常见的做法是/api/auth/login和/api/auth/register放行,其余接口都要登录后才能访问,前端每次请求必须带上 Token。
7.4 面板访问异常与服务器安全设置
有时候面板本身打不开,或者频繁被扫描,也值得花点时间处理:
- 面板打不开:确认安全组是否放行了面板自定义端口;用
netstat -tlnp | grep 端口号确认面板进程在监听;如果服务器有宝塔系统防火墙,去“安全”页面把面板端口加入放行列表。 - 登录时提示安全入口错误:宝塔的安全入口是 URL 后面的一段随机路径,从安装信息里能找到。如果忘记了,可以到服务器终端执行
bt default重新查看默认面板信息;也可以修改安全入口为自定义字符串。 - 经常收到面板登录失败告警:说明有人在对端口做暴力破解尝试。除了修改端口和安全入口,还可以在面板“安全”页面设置仅允许指定 IP 访问面板,把本地办公网络的公网 IP 加进去,其它 IP 一律拒绝。
面板本身只是管理工具,最重要的是记住它的安全基线:默认端口要改、安全入口要留、服务器安全组要收敛。做到了这些,才能让面板本身不成为系统的弱点。
我自己在实际部署中还有一个小习惯:每次发布新版本前,先在本地完整走一遍构建流程,确认 dist 目录生成正常、jar 包大小符合预期,再上传服务器。上线后第一时间用手机 4G 网络访问一下公网地址,确认不是本地缓存造成的假象。过程看起来繁琐,但几轮迭代下来,能省掉不少因为粗心导致的线上故障。部署这件事,熟练之后会很快,但每一步都值得认真对待。