
从本地把SpringBoot项目一路搬到云服务器上几乎是每个后端开发者都会经历的“成人礼”。我见过太多人卡在这一步本地IDE里跑得好好的服务换到云服务器后要么连不上数据库要么启动后访问不了要么进程莫名其妙就没了。这个系列教程就是要把 SpringBoot项目部署到云服务器 的完整链路拆开讲清楚从一台空白服务器开始让你最终能通过浏览器访问到自己部署的应用。这个系列第一篇我们把范围收敛得小一点只解决一个问题在一台全新的Linux云服务器上把本地写好的SpringBoot项目成功跑起来并且能通过公网访问。至于域名、HTTPS、Nginx反向代理、日志采集那些进阶内容会放到后面的篇目里单独处理。这篇非常适合第一次碰服务器、没怎么接触过Linux命令、以及“部署过一次但说不清原理”的同学。我会按一条实际可行的路径走先想清楚整条链路是怎么回事然后初始化服务器配置Java相关环境装数据库再打包上传项目最后搞定进程守护。每一步都会解释为什么要这么做不是单纯给命令。1. 动手前的全景图从本地jar到公网访问中间隔了哪几层很多教程上来就让你装JDK、传jar包结果出了问题你根本不知道去哪里排查。所以先花点时间把“本地能跑”和“服务器上能跑”这两件事的差异想清楚。1.1 部署的本质换一台电脑跑同一份代码本地开发时你的SpringBoot应用和MySQL、Redis等中间件全都跑在同一台机器上数据库地址填个localhost就能连。云服务器则是一台全新的Linux主机你需要在上面重新准备Java运行环境、数据库、配置文件以及让应用以后台进程方式存活。打个比方本地开发像在自己家厨房炒菜锅碗瓢盆随手就能拿到火候不对随时调。部署到云服务器相当于你换了一间新厨房调料要自己带齐燃气灶要先打开甚至做完菜之后还得保证饭店打烊之后菜不会自己坏掉。每一层准备工作没做到位最终菜就端不上桌。SpringBoot项目打包后是一个可执行的jar包里面包含了应用代码和对应的第三方依赖但它不包含JDK和中间件。所以部署的核心任务就是在服务器上搭好一个能让你这个jar包稳定运行的环境。1.2 第一篇的边界在哪里既然是系列教程的第一篇就得有个清晰的范围避免什么都往里塞。这篇只包括服务器实例的基本选择与安全组端口规划JDK 和 Maven 的安装与配置MySQL 的安装、初始化以及“安全远程访问”的策略本地打包 SpringBoot 项目并上传到服务器用 systemd 守护进程让应用崩了自动重启域名解析、Nginx反向代理、HTTPS证书、Docker化部署、持续集成CI/CD这些全部留到后续篇目。你按这个系列一步步走每一篇都只解决一个完整问题反而比那些“一篇搞定部署”的长文更扎实。1.3 服务器配置怎么选2核4G是起步线部署一个常规的SpringBoot单体应用我建议的起步配置是2核CPU、4G内存、40G SSD硬盘。如果你的项目涉及大量图片处理或复杂的定时任务再往上加到4核8G。低于1核2G我不太推荐因为JVM本身就要占掉一部分内存留给操作系统和数据库的空间会很紧张。操作系统方面选Ubuntu 20.04 LTS或更新的LTS版本即可资料多、遇到问题容易搜到解决方案。你手里已有的项目如果是基于JDK 8写的服务器上装JDK 8或11都能运行如果是SpringBoot 2.x以上版本建议直接JDK 11或17。这里我建议在后续安装环节统一按“JDK 17为主、JDK 8兼容为辅”的思路来配避免后面踩到版本不对齐的坑。2. 服务器开箱镜像选择与安全组端口规划服务器从云厂商控制台创建时有几个“开箱即设置”的选项。很多人图省事直接默认下一页结果后面跑不通回头又找一堆无关原因。这里我建议一次性把基础选项定清楚。2.1 创建实例时的三个关键选择第一个是镜像选择。别用带可视化桌面的镜像也不用装了宝塔之类面板的镜像就选纯净的Ubuntu Server系统镜像。干净的系统你自己清楚里面装了什么排查问题时候不会被额外软件干扰。有些云平台会提供“应用镜像”比如“LNMP环境”如果你已经有了SpringBoot这套技术栈这个镜像反而不相关甚至可能带来多余的Nginx或阿帕奇占用端口。第二个是登录方式。新手建议先使用密码登录等熟悉了再改造成密钥登录。在控制台设置root密码时注意有些云平台规定首次登录后需要强制重置密码要包含大小写字母和数字这个别嫌麻烦安全底线。第三个是安全组或防火墙规则。这是新手最先踩的坑。安全组相当于云平台的虚拟防火墙安全组里没放行的端口外面无论如何都访问不到。先只放行SSH端口22和后续SpringBoot项目需要的应用端口比如8080。不要图方便放行所有端口那样等于让服务器裸奔。2.2 安全组端口规划建议表下面的表格是我在初始化服务器时的一个规则参考。注意这里没有提到MySQL的3306端口原因在后面章节会详细说简单讲就是“数据库端口不应该直接暴露到公网”。端口协议用途建议22TCPSSH远程登录必须放行可考虑限制来源IP8080TCPSpringBoot应用访问必须放行作为主要业务入口3306TCPMySQL远程连接不建议放行使用隧道方式访问8800-8899TCP预留给后续中间件按需放行默认关闭这个表格基本就是“只开必要入口其余全关”的思路。如果你以后要部署多个微服务再按实际端口逐一放行别一次开个大网段。2.3 登录服务器后的基础环境检查登录服务器的第一步不是急着装东西而是做系统情况摸底。用SSH工具连上来之后先执行几条命令看看当下状态。whoami # 确认当前用户 sudo whoami # 确认有sudo权限 cat /etc/os-release # 查看系统版本 free -h # 查看内存 df -h # 查看磁盘我见过有人直接在root用户下操作这个争效率的角度会省心但生产环境还是建议创建一个带sudo权限的普通用户来跑应用。原因很现实SpringBoot进程一旦被入侵能用普通用户权限做的事肯定比root权限小得多。sudo useradd -m -s /bin/bash deploy sudo passwd deploy sudo usermod -aG sudo deploy创建好这个叫deploy的普通用户之后后续JDK、MySQL的安装可以用sudo操作但应用本身尽量用deploy用户去跑。这六个字母“deploy”后面还会反复出现。3. Java环境安装JDK版本对齐与Maven镜像加速环境准备好了第一件正事就是装Java。这里先强调一个原则服务器上的JDK版本必须和你本机开发用的版本对齐或者兼容。别觉得“能跑就行”真跑起来遇到UnsupportedClassVersionError你就知道什么叫欲哭无泪。3.1 为什么版本必须对齐SpringBoot项目的字节码编译是有版本要求的。你本地用JDK 17写的代码编译出的class文件版本高于服务器上JDK 8能加载的范围启动时JVM直接报异常提示你需要更高版本的Java运行环境。反过来本地在用高版本开发、服务器用高版本运行那自然顺利但你的代码如果用了高版本特有API而服务器被迫降级运行也会在某个角落莫名其妙出问题。我自己的项目管理原则很简单pom.xml里java.version什么版本服务器就装对应大版本最好连小版本都尽量接近。一棵树长在哪片土里就尽量别挪到另一片土里。3.2 JDK安装tar包解压方式最可控在Ubuntu上安装JDK有两个常见途径一是apt直接安装命令简单但版本可能受系统源限制而且不同发行版源的JDK版本差异较大二是下载JDK官方tar包手动解压并配置环境变量。这个系列我推荐第二种理由很直白可控。你明确知道自己下载的是哪个版本也清楚环境变量配到了哪里。排查问题时看java -version就能确认。下面以安装JDK 17为例# 下载JDK tar包文件名以实际获取到的版本为准 cd /opt sudo wget https://download.java.net/java/GA/jdk17/your-version-linux-x64_bin.tar.gz sudo tar -zxvf jdk-17_linux-x64_bin.tar.gz sudo mv jdk-17.x.x /usr/local/jdk17接下来配置环境变量。编辑/etc/profile.d/java.sh写入下面的内容export JAVA_HOME/usr/local/jdk17 export PATH$PATH:$JAVA_HOME/bin export CLASSPATH.:$JAVA_HOME/lib保存后执行source /etc/profile.d/java.sh然后验证。java -version javac -version看到版本正确输出说明JDK环境就绪。如果你手头的项目仍然需要JDK 8其实也是同样流程下载对应tar包再设置JAVA_HOME指过去即可。一台机器上同时存在多个JDK版本不冲突靠环境变量切换关键是你启动jar包时要知道当前默认的java是哪个。3.3 Maven安装与镜像设置服务器上装Maven主要是为了在服务器端直接拉取依赖和做打包测试。如果你本地打包好再上传服务器上其实可以不装Maven。我在这个系列第一篇里建议装上因为后面讲自动化构建时它会派上用场。Maven同样推荐tar包解压方式安装。安装好后重点在配置文件的镜像仓库。cd /opt sudo wget https://dlcdn.apache.org/maven/maven-3/your-version-bin.tar.gz sudo tar -zxvf apache-maven-3.x.x-bin.tar.gz sudo mv apache-maven-3.x.x /usr/local/maven然后编辑 Maven 的conf/settings.xml在mirrors节点里加一个镜像地址。很多新手部署时卡在依赖下载超时就是没有配置镜像。这里我建议使用云平台提供的公共Maven镜像速度稳定你只需要把下面的示例地址换成实际可用的镜像即可。mirror idpublic-mirror/id mirrorOfcentral/mirrorOf urlhttps://maven.example-mirror.com/repository/public//url /mirror配置好之后执行mvn -v确认版本执行mvn compile在你的项目目录里测试一下依赖能否顺利拉取。镜像配置的收益在你第一次打包做过你就懂了很多不会配置的人在那个过程里浪费掉的时间足够吃一顿午饭。4. MySQL部署与远程访问策略不直接暴露3306的玩法SpringBoot项目十有八九要连数据库。服务器上只装好Java还不够数据库也得准备好。这里我不只讲“怎么装MySQL”更关键的是“怎么安全地连上它”。4.1 安装MySQL并完成基础初始化使用apt安装MySQL服务过程相当简单sudo apt update sudo apt install mysql-server安装完成后MySQL服务默认会自动启动你可以用sudo systemctl status mysql看到它的状态。接下来执行安全初始化脚本sudo mysql_secure_installation脚本会问你是否设置密码强度、删除匿名用户、禁止root远程登录、清理测试数据库。这几个问题全部选择yes即可。尤其是“禁止root远程登录”这题很多不看说明的人选了no结果安全问题就埋下了。Ubuntu下MySQL默认的root账号认证方式是auth_socket意思是只有系统的root用户或sudo组用户能直接登录MySQL的root。这样本地管理很方便但你用客户端远程连root是连不上的——这其实是种很稳的安全策略建议保留。4.2 创建业务数据库和专用账号项目运行时不要用root账号连接数据库而是为项目单独建一个账号并且只授予业务库的权限。下面命令里的“appdb”和“app_user”换成你自己的库名和账号名。sudo mysql CREATE DATABASE appdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER app_user% IDENTIFIED BY 这里写一个强密码; GRANT ALL PRIVILEGES ON appdb.* TO app_user%; FLUSH PRIVILEGES; EXIT;这里%表示允许这个账号从任何主机连接。这个配置配合上“不直接暴露3306”的策略才是完整的安全方案。如果你把它理解成“那远程就能直连了”就落入了最常见的陷阱。4.3 本地连接服务器数据库的安全姿势很多原生的部署教程会让你修改MySQL配置把bind-address改成0.0.0.0并把3306端口放行到安全组然后本地用Navicat之类的工具远程连接。我能理解这样方便但在公网环境里这样做风险很大数据库端口被人扫到以后接下来就是数不清的暴力破解尝试。我推荐的做法是不直接放行3306而是用SSH隧道做安全转发把本地的端口流量加密传送到服务器上。# 本地命令示例把本地3306端口转发到服务器的127.0.0.1:3306 ssh -L 3306:127.0.0.1:3306 deploy你的服务器IP保持这个SSH会话不关闭然后在本地数据库客户端里连接127.0.0.1:3306无需修改MySQL的bind-address也不用在安全组里开3306端口。数据库等于只对服务器本机可见外部完全扫不到。这个方案第一次配置起来有点绕但一旦用顺了你会庆幸当初没有把3306白白暴露出去。有人可能会问项目在服务器上运行连接数据库时不也走localhost吗对服务器上的SpringBoot项目以本机回环地址连接MySQL天然安全。SSH隧道只是给你本地调试用的通道两者互不干扰。5. 打包上传配置Profile与scp的实操细节环境这边准备好后回到你本地开始把项目变成服务器能运行的产物。这一步做得工整后面等于少掉一半的排查时间。5.1 打包前先检查这几处配置我见过不少部署失败不是环境问题而是打包时把开发环境配置原封不动地带上了生产环境。所以打包前请检查下面几项第一配置文件里的数据库地址。本地可能是localhost服务器上应该改成服务器的内网IP或直接也填localhost。更好的做法是用application.yml和application-prod.yml做不同环境的profile拆分示例如下。# application.yml spring: profiles: active: dev# application-prod.yml spring: datasource: url: jdbc:mysql://127.0.0.1:3306/appdb?useUnicodetruecharacterEncodingutf8 username: app_user password: 你的数据库密码第二确认打包方式。在pom.xml里确认SpringBoot的打包插件存在否则打出来的是普通jar启动时告诉你“没有主清单属性”。build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build第三检查端口配置。默认8080没有问题但如果你在代码里写死了某个端口服务器安全组也得对应放行。我建议显式在application-prod.yml里指定端口方便团队其他人一眼看懂。5.2 执行打包命令在项目根目录下执行mvn clean package -DskipTests如果没有配置镜像或依赖下载慢这里可能会卡挺久。耐心等它跑完最后在target目录下会生成一个类似app-0.0.1-SNAPSHOT.jar的文件。顺便提一句-DskipTests是跳过单元测试。如果你项目里的测试用例本身依赖本地数据库或Redis打包时建议跳过否则容易在测试阶段把打包流程卡住。测试的完整性可以留到CI阶段再做本地打包求快求稳。5.3 jar包安全上传的几种方式上传jar包到服务器常见的有scp、rsync和SFTP图形工具三种。命令行下我认为最顺手的是scp它基于SSH协议不用额外开启服务。scp -P 22 target/app-0.0.1-SNAPSHOT.jar deploy服务器IP:/opt/app/第一登录用户得有目标目录的写权限第二如果jar包特别大rsync带断点续传会比scp更稳第三你可以直接把jar包上传到/opt/app这个目录作为全站应用的统一部署目录假设你用deploy用户操作要把目录所有者改为deploy。sudo mkdir -p /opt/app sudo chown -R deploy:deploy /opt/app上传结束后在服务器上确认文件在、文件大小正常即可不必急着运行。6. 进程守护与首次访问验证systemd让jar自动重启jar包到了服务器上最直接的做法是java -jar跑一下但这种方式有两个问题一是退出SSH会话进程就没了二是进程崩溃了不会自动拉起来。要解决这事用Linux自带的 systemd 最合适这不是锦上添花而是让项目“像一个正式服务一样活着”。6.1 编写systemd服务文件用sudo在/etc/systemd/system/下新建一个服务文件这里我把它命名为yourapp.service文件名里的“yourapp”换成你自己的应用名。[Unit] DescriptionSpringBoot Application Afternetwork.target [Service] Typesimple Userdeploy Groupdeploy WorkingDirectory/opt/app ExecStart/usr/local/jdk17/bin/java -Xms256m -Xmx512m -jar /opt/app/app-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod Restartalways RestartSec5 SuccessExitStatus143 [Install] WantedBymulti-user.target这个文件里的关键字段值得逐项说清楚。Userdeploy以普通用户运行不用root启动业务进程。WorkingDirectory指定运行时的工作目录应用里如果用到了相对路径的文件操作比如日志输出目录它的相对根就在这里。ExecStartJVM用绝对路径避免PATH找不到java。-Xms和-Xmx代表JVM初始内存和最大内存根据服务器内存调整别把小内存机器直接塞爆。Restartalways进程因任何原因退出都自动拉起。这是最狠的守护策略你用kill手动杀掉它也会被拉起来。SuccessExitStatus143处理“服务被正常停止”的退出码。当你执行systemctl stop时JVM收到的退出信号对应的状态码就是143把它声明为正常否则systemd会误判为异常崩溃。6.2 启动服务并验证访问写好服务文件后依次执行下面命令sudo systemctl daemon-reload sudo systemctl enable yourapp sudo systemctl start yourapp sudo systemctl status yourappenable是把服务注册为开机自启start才是立刻拉起。执行完status后如果看到绿色的active (running)说明服务已经起来了。此时在服务器本机验证一下curl http://127.0.0.1:8080/actuator/health curl http://127.0.0.1:8080/第一条命令要看你的项目是否引入了SpringBoot Actuator依赖如果引了会返回类似{status:UP}的健康信息。第二条访问首页如果首页接口需要数据库这也会顺带验证整条链路。本机访问通了再用你本地的浏览器访问http://服务器公网IP:8080/。这一步如果打不开不外乎三个原因服务没起、安全组没放行8080、防火墙还在拦。6.3 常见故障排查清单部署时出问题不可怕可怕的是毫无头绪地瞎试。下面是我实际排查时会按顺序检查的清单按“由近到远”的排查思路来。排查对象命令关注点服务运行状态systemctl status yourapp是不是active还是failed应用日志journalctl -u yourapp -f看异常栈与启动失败原因端口监听ss -lntp | grep 8080确认jar包是否真的监听了端口进程存在性ps -ef | grep java确认进程是否被守护重复拉起数据库连接在服务器本机试连MySQL排除数据库网络问题磁盘空间df -hjar包解压运行时若磁盘满进程反复崩溃有一个非常典型的坑出现在日志里UnsupportedClassVersionError说明JDK版本不对齐。还有一个坑是“端口被占用”你本机测试没问题但服务器就是启动失败用ss -lntp一看发现8080被其他程序占用了。这两个问题都能从日志或端口命令里一眼定位。日志查看是我觉得最值得养成习惯的环节。启动时你不需要死盯终端执行journalctl -u yourapp -f实时滚动查看日志即可。看到Started Application in X seconds时才是真正能放心的时刻。到这里你在云服务器上已经拥有一个能开机自启、崩溃自愈的SpringBoot服务了。坦白讲第一次部署时那种各种版本不搭、目录权限不对、端口不通的挫败感我全经历过。但你的心思不用停固在这篇的末尾后面的坑还多着Nginx反向代理怎么做、HTTPS证书怎么让浏览器认账、日志量大了之后怎么收集这些我会在系列后续一篇接一篇写出来。先把这条最基础链路跑通后面加任何东西都是在它的基础上打补丁而已。