从空环境部署Spring Boot应用:最少依赖、启动配置与可用性验证
介绍在基础Linux服务器上部署可执行Spring Boot JAR所需的最少依赖,涵盖Java运行时、外部配置、systemd进程托管、启动检查与健康验证,并提供常见故障排查和失败回滚步骤,适合首次部署与运维人员参考。

一台只有基础 Linux 系统的新服务器,要运行一个已经构建完成的 Spring Boot 应用,最少只需要匹配应用要求的 Java 运行时、可执行 JAR、外部配置和进程托管机制。生产服务器不必安装 Maven、Gradle 或完整开发工具链;构建工作应在开发环境或 CI 中完成,再把产物部署到服务器。
下面以使用 systemd 的 Ubuntu 22.04/24.04、Rocky Linux 9 或 AlmaLinux 9 为例。操作对象是可通过 java -jar 启动的 Spring Boot JAR,不适用于 WAR、容器镜像或 GraalVM Native Image。示例应用名为 myapp,端口为 8080,实际部署时应替换为项目自己的名称、端口和配置。
先确认最少依赖是否成立
“最少依赖”并不等于所有 Spring Boot 应用只安装 Java 就能运行。应用如果使用数据库、Redis、消息队列或第三方接口,这些仍然是启动或业务可用所需的外部依赖,只是不一定安装在当前服务器上。
部署前至少准备以下内容:
| 项目 | 是否必需 | 判断方法 |
|---|---|---|
| 可执行 Spring Boot JAR | 是 | 构建环境中可以通过 java -jar 启动 |
| 匹配版本的 Java 运行时 | 是 | 查看 pom.xml、build.gradle 或构建流水线配置 |
| 应用配置 | 是 | 核对端口、环境标识及外部服务地址 |
| systemd | 建议 | 用于开机启动、异常重启和日志管理 |
| curl | 建议 | 用于 HTTP 可用性验证 |
| Maven、Gradle、Git | 否 | 已有构建产物时无需安装 |
| Nginx | 否 | 只有反向代理、TLS 或多站点需求时才需要 |
Spring Boot 3.x 的基础运行要求是 Java 17 或更高版本,但应用也可能明确构建为 Java 21。运行时版本应以项目声明并经过验证的版本为准,不能仅因为服务器支持就随意换成更高版本。
先核对系统和架构:
cat /etc/os-release
uname -m
systemctl --version
df -h /
free -h
如果服务器不是上述发行版,先确认包管理器、Java 软件包名称和 systemd 支持情况,不要直接复制安装命令。
安装匹配的 Java 运行时
以下示例安装 Java 17 的无图形运行环境。如果应用要求 Java 21,应安装对应的软件包,而不是继续使用 Java 17。
Ubuntu 22.04/24.04:
sudo apt update
sudo apt install -y openjdk-17-jre-headless curl
Rocky Linux 9、AlmaLinux 9:
sudo dnf install -y java-17-openjdk-headless curl
安装后检查版本和实际执行路径:
java -version
command -v java
readlink -f "$(command -v java)"
后面的 systemd 示例使用 /usr/bin/java。如果 command -v java 返回其他路径,应将服务文件中的 ExecStart 改成实际路径。
普通 Spring Boot 应用只需要 Java 运行时。只有应用在运行期间确实调用编译器、依赖 JDK 工具,或项目文档明确要求时,才需要安装完整 JDK。
创建独立用户并放置应用文件
不要长期使用 root 运行 Spring Boot 应用。独立用户可以限制文件访问范围,也能避免应用漏洞直接获得系统最高权限。
创建系统用户和部署目录:
if ! id myapp >/dev/null 2>&1; then
sudo useradd --system --user-group --home-dir /opt/myapp \
--shell /bin/false myapp
fi
sudo install -d -o root -g myapp -m 0750 /opt/myapp
sudo install -d -o root -g myapp -m 0750 /etc/myapp
假设构建产物已经上传到 /tmp/myapp.jar。先计算摘要,并与构建端提供的摘要比对,避免文件上传不完整:
sha256sum /tmp/myapp.jar
ls -lh /tmp/myapp.jar
如果 /opt/myapp/app.jar 已存在,覆盖前先备份。下面的备份操作不会删除原文件:
if sudo test -e /opt/myapp/app.jar; then
STAMP="$(date +%Y%m%d-%H%M%S)"
sudo install -d -o root -g root -m 0700 "/var/backups/myapp/$STAMP"
sudo cp -a /opt/myapp/app.jar "/var/backups/myapp/$STAMP/app.jar"
echo "备份目录:/var/backups/myapp/$STAMP"
fi
安装新 JAR,并确保应用用户只有读取权限:
sudo install -o root -g myapp -m 0640 \
/tmp/myapp.jar /opt/myapp/app.jar
sudo -u myapp test -r /opt/myapp/app.jar
将 JAR 设置为 root 所有,可以降低应用进程自行修改部署文件的风险。Spring Boot JAR 不需要执行权限,由 Java 负责读取和启动。
使用外部配置控制首次启动
配置文件不要直接修改在 JAR 内部。外部配置便于升级、排障和回滚,也能避免每次修改端口都重新构建应用。
使用以下命令编辑配置:
sudoedit /etc/myapp/application.yml
写入最小配置:
server:
address: 127.0.0.1
port: 8080
spring:
application:
name: myapp
127.0.0.1 表示只接受本机连接,适合后续由本机反向代理访问,也能降低首次启动时意外暴露管理接口的风险。如果应用确实需要直接接受外部请求,可在确认认证、访问控制和网络策略后改成:
server:
address: 0.0.0.0
port: 8080
这只改变应用监听地址,不会自动放行操作系统防火墙或上游访问规则。
设置配置文件权限:
sudo chown root:myapp /etc/myapp/application.yml
sudo chmod 0640 /etc/myapp/application.yml
再创建 systemd 环境变量文件:
sudoedit /etc/myapp/myapp.env
写入运行环境:
SPRING_PROFILES_ACTIVE=prod
设置权限:
sudo chown root:myapp /etc/myapp/myapp.env
sudo chmod 0640 /etc/myapp/myapp.env
数据库密码、接口密钥等敏感内容不宜出现在启动命令中,否则可能被进程信息、历史记录或运维工具采集。如果必须通过环境变量传递,应写入权限受控的环境文件,且不要把真实密钥提交到代码仓库。
Spring Boot 的环境变量和命令行参数通常比配置文件具有更高优先级。遇到“配置文件明明修改了但没有生效”时,应同时检查 systemd 环境文件、启动参数和当前启用的 Profile。
交给 systemd 管理进程
创建服务文件前,如果同名文件已经存在,应先备份:
if sudo test -e /etc/systemd/system/myapp.service; then
sudo cp -a /etc/systemd/system/myapp.service \
"/etc/systemd/system/myapp.service.bak.$(date +%Y%m%d-%H%M%S)"
fi
编辑服务单元:
sudoedit /etc/systemd/system/myapp.service
写入以下内容:
[Unit]
Description=My Spring Boot Application
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/java -jar /opt/myapp/app.jar --spring.config.additional-location=file:/etc/myapp/
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
KillSignal=SIGTERM
UMask=0027
[Install]
WantedBy=multi-user.target
spring.config.additional-location 指向目录时,末尾的 / 不应省略。这里没有使用 optional:,因此配置目录缺失时启动会直接失败,避免应用静默使用默认配置运行。
首次部署不建议直接复制固定的 -Xms、-Xmx 参数。JVM 堆大小需要根据服务器可用内存、应用非堆内存、线程数量和同机其他进程计算。没有明确内存预算时,盲目设置较大的堆上限反而可能触发系统内存不足。
检查服务文件语法:
sudo systemd-analyze verify /etc/systemd/system/myapp.service
如果没有与 myapp.service 相关的错误,重新加载 systemd 并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
enable --now 会同时设置开机启动并立即启动应用。已有同名服务时,该操作可能中断当前业务,应安排维护时间后再执行。
按层次验证应用是否真正可用
进程存在不等于 Spring Boot 应用可用。至少要依次确认服务状态、端口监听、HTTP 响应和业务依赖。
检查 systemd 状态
sudo systemctl status myapp --no-pager -l
sudo systemctl is-enabled myapp
sudo systemctl is-active myapp
预期 is-enabled 返回 enabled,is-active 返回 active。但 active 只说明 Java 进程没有退出,仍需继续检查 HTTP。
查看首次启动日志
sudo journalctl -u myapp -b -n 200 --no-pager
需要持续观察时使用:
sudo journalctl -u myapp -f
正常日志通常可以看到 Spring Boot 完成启动、Web 服务器监听指定端口等信息。若日志不断重复启动,说明 Restart=on-failure 正在重试,应先停止服务再修复配置,避免错误循环刷日志:
sudo systemctl stop myapp
确认端口监听
sudo ss -lntp 'sport = :8080'
如果配置为 127.0.0.1,结果应显示端口监听在本机回环地址。若没有输出,说明应用尚未成功监听,需要回到日志定位原因。
验证 HTTP 接口
如果应用已经包含 Spring Boot Actuator,并开放了健康端点:
curl -fsS --max-time 5 http://127.0.0.1:8080/actuator/health
成功时通常会返回包含 UP 状态的 JSON。若项目没有 Actuator,应请求一个无需修改数据、能代表业务就绪状态的只读接口:
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
--max-time 5 http://127.0.0.1:8080/
单纯返回 404 只能说明 Web 容器能够响应,不能证明业务接口正常。更可靠的验收条件是:请求项目已知接口获得预期状态码和响应内容,并确认数据库、缓存等必要依赖已经连通。
如果接口启用了认证,返回 401 或 403 可能是正常的访问控制结果,不应直接判断为应用启动失败。此时应使用经过授权的健康检查方式验证。
首次启动失败时的检查顺序
故障处理应从低风险、最容易确认的项目开始,不要先重装 Java、删除配置或强制结束未知进程。
Java 版本不匹配
典型日志包括:
UnsupportedClassVersionError
这表示 JAR 使用了高于当前运行时支持的字节码版本。处理方式是安装项目要求的 Java 版本,或在构建端重新指定目标版本。不要只修改服务文件中的版本名称而不检查实际 java -version。
JAR 路径或权限错误
检查文件是否存在,以及服务用户能否读取:
ls -l /opt/myapp/app.jar
namei -l /opt/myapp/app.jar
sudo -u myapp test -r /opt/myapp/app.jar && echo "JAR 可读"
如果 ExecStart 路径、文件名或大小写错误,systemd 日志中通常会出现找不到文件或无法访问的信息。
端口已被占用
常见日志为:
Address already in use
确认占用进程:
sudo ss -lntp 'sport = :8080'
不要直接结束未知进程。应先确认它属于哪个服务,再决定修改 Spring Boot 端口,还是在评估影响后停止原服务。
YAML、Profile 或环境变量有误
配置缩进错误、属性名称错误或必需变量为空,可能导致 Spring 容器初始化失败。重点检查:
sudo sed -n '1,160p' /etc/myapp/application.yml
sudo systemctl cat myapp
sudo systemctl show myapp -p EnvironmentFiles
包含密码的环境文件不应直接打印到工单、聊天记录或公开日志中。排查时只确认变量是否存在,避免暴露真实值。
外部组件连接失败
如果日志停留在数据源、Redis、消息队列或配置中心初始化阶段,说明 Java 和 JAR 已经执行,但应用依赖未满足。应根据异常链最底层的 Caused by 检查:
- 地址和端口是否填写正确;
- DNS 是否能够解析;
- 账号是否有权限;
- TLS 证书或协议是否匹配;
- 外部组件是否要求来源访问控制。
network-online.target 只表示系统网络初始化完成,不保证数据库等远端服务已经可访问。
本机可用但外部无法访问
先确认 server.address。监听在 127.0.0.1 时,外部无法直接连接属于预期行为;监听在 0.0.0.0 时,再检查服务器防火墙、上游访问规则和反向代理配置。不要为了排障直接关闭全部防火墙规则,应只核对并调整实际需要的端口范围。
失败回滚与恢复
首次部署失败且暂时不继续处理时,可以停止并取消开机启动:
sudo systemctl disable --now myapp
该操作不会删除 JAR、配置和日志,后续仍可继续排查。
如果是替换已有版本后失败,应恢复部署前记录的备份。以下操作会覆盖当前 JAR,执行前必须把 ROLLBACK 改为已经确认存在的备份目录:
ROLLBACK="20260101-120000"
sudo test -r "/var/backups/myapp/$ROLLBACK/app.jar" \
|| { echo "指定备份不存在"; exit 1; }
sudo systemctl stop myapp
sudo install -o root -g myapp -m 0640 \
"/var/backups/myapp/$ROLLBACK/app.jar" \
/opt/myapp/app.jar
sudo systemctl start myapp
恢复后重新检查日志、端口和健康接口:
sudo systemctl status myapp --no-pager -l
sudo journalctl -u myapp -b -n 100 --no-pager
sudo ss -lntp 'sport = :8080'
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
--max-time 5 http://127.0.0.1:8080/
配置文件和 systemd 单元也发生过变更时,应分别恢复对应备份,再执行:
sudo systemctl daemon-reload
sudo systemctl restart myapp
重启会造成当前应用连接中断,已有业务流量时应先确认影响范围。
上线验收检查清单
- Java 版本与应用构建要求一致,实际路径与
ExecStart一致; - Spring Boot 应用由独立低权限用户运行,而不是 root;
- JAR、配置文件和环境文件的所有者及权限正确;
- systemd 服务已启用,停止、启动和异常退出行为符合预期;
- 日志中没有持续重启、配置解析失败或外部依赖连接异常;
- 端口只监听在业务需要的地址上;
- 健康接口或只读业务接口返回预期状态和内容;
- 数据库、缓存等必要外部组件已纳入可用性验证;
- 新版本部署前已保留 JAR、配置和服务文件备份;
- 回滚路径已经记录,恢复后仍会重新验证日志、端口和业务接口。