Spring Boot部署:启动、代理与回退
Spring Boot 项目能在终端里启动,还需要解决 SSH 断开后继续运行、重启后恢复、域名访问和版本回退。本文使用Ubuntu 24.04 x86_64、Java 21,以及按 Java 21 目标构建的 Spring Boot 4.1.2 可执行 JAR,应用使用内嵌 Tomcat。已有老项目先核对依赖兼容性,不要仅为套教程直接升级框架。
以下以 app.jar、域名 java.example.com和只读检查地址 /healthz为例。域名要换成实际值;应用没有 /healthz 时,用项目确实存在、不会触发订单或其他写入的 GET 地址替换后面的检查命令。数据库、缓存及 prod 环境配置需先准备妥当。
确认上传的是可执行JAR
在开发电脑完成项目测试和打包,例如使用项目自带的 Maven Wrapper 执行 ./mvnw clean package。上传 Spring Boot 重新打包后的可执行 JAR,不是 plain JAR 或源码压缩包。通过SFTP保存到服务器当前账号的 ~/baole-spring-upload/app.jar。
在服务器安装 Java 运行环境和检查工具。已有面板或其他 Nginx 时先核对管理方式,本例采用 Ubuntu 的系统 Nginx:
cat /etc/os-release
uname -m
sudo apt-get update &&
sudo apt-get install openjdk-21-jre-headless nginx curl nano &&
/usr/lib/jvm/java-21-openjdk-amd64/bin/java -version
sha256sum "$HOME/baole-spring-upload/app.jar"
把上传前后的 SHA-256 对照,确认文件传输一致。使用绝对 Java 路径是为了避免系统默认 java 指向其他版本;如果 JAR 是用 Java 25 编译且没有设为 Java 21 目标,换成 Java 21 运行会报类版本不兼容。
程序、业务数据和配置各放在固定位置
首次部署创建专用系统账号。该账号不用于 SSH 登录,程序由 root 部署,业务写入目录交给服务账号。下面路径或账号已经存在时先检查,不覆盖旧服务:
test -s "$HOME/baole-spring-upload/app.jar" &&
sudo useradd --system --user-group --home-dir /var/lib/baole-spring --shell /usr/sbin/nologin baole-spring &&
sudo install -d -m 0755 /opt/baole-spring/releases/release-01 &&
sudo install -o root -g root -m 0644 "$HOME/baole-spring-upload/app.jar" /opt/baole-spring/releases/release-01/app.jar &&
sudo ln -sT /opt/baole-spring/releases/release-01 /opt/baole-spring/current &&
sudo install -d -o root -g baole-spring -m 0750 /etc/baole-spring &&
sudo touch /etc/baole-spring/application.properties /etc/baole-spring/app.env &&
sudo chown root:baole-spring /etc/baole-spring/application.properties &&
sudo chmod 0640 /etc/baole-spring/application.properties &&
sudo chmod 0600 /etc/baole-spring/app.env
编辑 /etc/baole-spring/application.properties,写入本例的监听和代理配置。应用只接受同机 Nginx 转发,8080 不向公网开放:
server.address=127.0.0.1
server.port=8080
server.forward-headers-strategy=native
server.tomcat.remoteip.internal-proxies=127[.]0[.]0[.]1
server.tomcat.redirect-context-root=false
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
再编辑 /etc/baole-spring/app.env,写入 SPRING_PROFILES_ACTIVE=prod。项目使用其他 profile 时按实际调整。数据库等业务参数放在受保护的外部配置或项目支持的环境变量中,不能把密码拼到启动命令里。这里的 env 文件由 systemd 读取,不是任意 Shell 脚本。
让systemd管理进程
新建 /etc/systemd/system/baole-spring.service:
[Unit]
Description=Baole Spring Boot application
After=network.target
[Service]
Type=exec
User=baole-spring
Group=baole-spring
StateDirectory=baole-spring
StateDirectoryMode=0750
WorkingDirectory=/var/lib/baole-spring
EnvironmentFile=/etc/baole-spring/app.env
ExecStart=/usr/lib/jvm/java-21-openjdk-amd64/bin/java -Xms128m -Xmx512m -jar /opt/baole-spring/current/app.jar --spring.config.additional-location=file:/etc/baole-spring/application.properties
Restart=on-failure
RestartSec=5
TimeoutStopSec=45
SuccessExitStatus=143
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
本例 JVM 堆上限为 512 MB,整台服务器还要为系统、线程栈、元空间和其他服务预留内存,不能把全部内存都给 Xmx。相对路径的业务写入会落在 /var/lib/baole-spring;项目自己的上传目录、文件日志和本地数据库路径也要核对,不能假设它们自动随配置改变。
sudo systemctl daemon-reload &&
sudo systemctl enable --now baole-spring
sudo systemctl status baole-spring --no-pager
sudo journalctl -u baole-spring -n 80 --no-pager
sudo ss -lntp 'sport = :8080'
curl --fail --show-error http://127.0.0.1:8080/healthz
等启动日志确认完成后再检查接口。应看到 Java 只监听 127.0.0.1:8080,检查地址返回预期内容。服务显示 active 但随后启动失败、数据库认证失败或接口报错,均不能算部署完成。继续看服务日志,不要靠连续重启掩盖错误。
域名交给Nginx,Java保留本机入口
新建 /etc/nginx/sites-available/baole-spring,替换域名:
server {
listen 80;
server_name java.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Forwarded "";
proxy_set_header Connection "";
}
}
sudo ln -sT /etc/nginx/sites-available/baole-spring /etc/nginx/sites-enabled/baole-spring &&
sudo nginx -t &&
sudo systemctl reload nginx &&
curl --resolve java.example.com:80:127.0.0.1 --fail --show-error http://java.example.com/healthz
只有配置检查成功才重载。再从外部访问实际域名,按安全组教程放行网站端口,并完成HTTPS 配置。检查登录跳转是否仍指向内部端口、HTTPS 页面是否被错误改回 HTTP。本文的代理信任只针对同机 Nginx;增加 CDN、多级代理或换成 WebFlux、Jetty 时,需要重新核对真实代理地址和应用配置。
更新时换版本目录,不覆盖正在运行的JAR
将新 JAR 上传并核对摘要,再安装到新的 release-02目录。确认数据库变更与旧版本兼容后,安排短暂维护窗口,保存当前程序指向并切换:
test -L /opt/baole-spring/current &&
readlink -f /opt/baole-spring/current &&
test -s "$HOME/baole-spring-upload/app.jar" &&
test ! -e /opt/baole-spring/releases/release-02 &&
test ! -L /opt/baole-spring/releases/release-02 &&
sudo install -d -m 0755 /opt/baole-spring/releases/release-02 &&
sudo install -o root -g root -m 0644 "$HOME/baole-spring-upload/app.jar" /opt/baole-spring/releases/release-02/app.jar &&
sudo ln -sT /opt/baole-spring/releases/release-02 /opt/baole-spring/current.next &&
test "$(readlink -f /opt/baole-spring/current.next)" = /opt/baole-spring/releases/release-02 &&
test -s /opt/baole-spring/current.next/app.jar &&
sudo -u baole-spring test -r /opt/baole-spring/current.next/app.jar &&
sudo systemctl stop baole-spring &&
sudo mv -Tf /opt/baole-spring/current.next /opt/baole-spring/current &&
sudo systemctl start baole-spring
保存第一段输出的旧版本路径。每个版本使用新目录;current.next已经存在时先查明原因。上述检查会确认新 JAR 非空、服务账号可读,以及 current.next 的真实目标正确,全部成功后才停服。任一步失败,本段后续命令都不会继续。切换后重复本机接口、外部 HTTPS 和必要业务操作的检查。若新程序失败,且数据库、外部配置仍兼容,可以用同样的停服切换步骤让 current 指回原版本,再启动复测。
退回 JAR 不会自动退回数据库。有自动建表、删字段或数据转换时,上线前先准备对应备份和迁移方案;不能在服务已经产生新数据后直接回灌旧库。出现 no main manifest attribute先检查打包产物;出现 Permission denied核对服务账号需要读取或写入的具体目录,不改成 root 运行。