blog.navy82.icu / index / 06 ↑ 返回索引
#06 2026-08-18 系统迁移 / Java / Nginx / CentOS 阅读 –

Windows → Linux 迁移:双 Java 应用 + Nginx 上线

迁移完成双应用上线反代+自启Windows 遗留

把跑在一台 Windows 桌面机上的 2 个 Java 应用(app.jar / admin.jar)+ 1 个 Nginx 全量迁到 Linux(CentOS Stream 9)。环境重建(JDK 1.8 固定版本、Nginx 句柄上限 65535)、Jar 迁移、systemd 开机自启、Nginx 多域名 HTTPS 反代(含 WebSocket)与双层防火墙收敛,端口/目录与源保持一致,业务无感切换。

本案例目录

CHART 01 // 服务 · 端口 · 迁移后归属

service · port · destination
app.jar8080APP 对外服务/data/java/app
admin.jar8081后台管理/data/java/admin
nginx80/443多域名 HTTPS 反代反代 → 8080 / 8081
ws8081WebSocket 推送/ws/ 反代 + Upgrade 头
ssh22OpenSSH远程管理
firewalld—防火墙放行 22/80/443/8080/8081

CHART 02 // 迁移关键处置

pitfalls handled during migration
JDK8顶掉被 11/17 抢占默认版本alternatives + versionlock
NOHUP挂起SSH 会话被进程挂住& + </dev/null 必须
NOFILE1024nginx 句柄上限默认过低drop-in + rlimit_nofile
WEBSOCKET握手反代缺 Upgrade 头会失败proxy_http_version 1.1
PORT80冲突反代前 80 被占用ss 预检再配置
SELINUX拦截enforcing 拦反代/访问调试期 setenforce 0

迁移任务:[客户名称](已隐去)· 完成日期:2026-08-18 迁移方式:全量搬迁(目标机环境重建 + 应用与反代迁移,端口/目录与源保持一致) 脱敏说明:客户名、真实域名、服务器 IP 与实例信息均已隐去。

环境与系统信息

迁移前(源)

项目 信息
运行载体 Windows 桌面机(同时承担开发 / 部署 / 对外服务)
操作系统 Windows 10 专业版(具体版本已隐去)
业务形态 2 个 Java 应用 + 1 个 Nginx,全部跑在这台桌面机上
应用组成 app.jar(APP 对外服务,端口 8080)、admin.jar(后台管理,端口 8081)
Web Nginx 监听 80,按域名反代到两个端口
数据依赖 外部 MySQL / 中间件(保持原样,不在本次迁移范围)

迁移后(目标)

项目 信息
操作系统 CentOS Stream 9 · x86_64(dnf 包管理)
JDK 1.8.0(java-1.8.0-openjdk-devel,versionlock 固定版本)
应用目录 /data/java/app(app.jar)、/data/java/admin(admin.jar)
服务端口 app 8080 / admin 8081(与迁移前完全一致)
Web Nginx:多域名 HTTPS + 双后端反代(8080/8081)+ WebSocket
防火墙 firewalld,放行 22 80 443 8080 8081
开机自启 systemd(app-java.service / admin-java.service)

迁移背景

两个 Java 应用(Spring Boot JAR)和一个 Nginx 一直跑在一台 Windows 桌面机上。桌面机当服务器用有几个硬伤:

  1. 稳定性差:重启后依赖用户登录会话,无人登录/无人手工启动,服务就不起来;
  2. 无统一托管:靠任务计划或手工 nohup,进程管理、开机自启、异常拉起全靠人工;
  3. 资源被抢占:桌面负载(浏览器/办公软件)与应用抢 CPU 和内存;
  4. 运维不透明:没有统一日志、没有防火墙收敛,安全基线无从谈起。

目标很明确:把 2 个 Java 应用 + Nginx 完整迁到 Linux 服务器,用 systemd 托管,做到重启自愈、端口收敛、日志统一,业务侧无感切换。

迁移思路

全量搬迁,三步走:目标机环境就绪 → 应用与配置迁移 → 反代接入与验证。

  • 把每步命令固化成幂等脚本(可重复执行不报错),同时拆成手动分步说明,供逐条执行、逐条验证;
  • 端口、目录、域名结构与迁移前保持一致,切换只发生在 DNS/IP 解析层,业务侧无感;
  • 先在一台测试机跑通全流程,再上目标机,避免"边改边生产"。

迁移实施

第 1 步 · 目标机环境:安装并固定 JDK 1.8

dnf -y --setopt=skip_if_unavailable=True install java-1.8.0-openjdk-devel

关键坑:机器上可能同时存在 JDK 11/17,而 JDK 8 在 alternatives 里优先级最低,不强制切换的话 java -version 看到的还是旧版本:

J8=$(readlink -f /usr/lib/jvm/java-1.8.0-openjdk)
alternatives --set java  "$J8/jre/bin/java"
alternatives --set javac "$J8/bin/javac"

再补一刀:固定版本,防止日后 dnf update 悄悄把 JDK 升走,应用运行时版本被偷偷换掉:

dnf -y --setopt=skip_if_unavailable=True install "dnf-command(versionlock)"
dnf versionlock add java-1.8.0-openjdk java-1.8.0-openjdk-devel java-1.8.0-openjdk-headless

验证:java -version / javac -version 均须显示 1.8.0_xxx。

第 2 步 · 目标机环境:Nginx 安装与连接数优化

dnf -y --setopt=skip_if_unavailable=True install nginx

默认句柄上限只有 1024,高并发下 Nginx 会先顶不住。只改 /etc/security/limits.conf 对 nginx 无效(systemd 服务不读它),要做四处:

  1. events { worker_connections 65535; multi_accept on; }
  2. worker_rlimit_nofile 65535;(关键:只有这个对 nginx 进程直接生效)
  3. systemd drop-in:/etc/systemd/system/nginx.service.d/override.conf 写 [Service] LimitNOFILE=65535,然后 systemctl daemon-reload
  4. limits.conf 补 * soft/hard nofile 65535

启动后核对句柄上限,应显示 65535 而不是 1024:

grep 'Max open files' /proc/$(systemctl show -p MainPID --value nginx)/limits | awk '{print $4}'

第 3 步 · 防火墙放行

systemctl start firewalld; systemctl enable firewalld
for p in 22 80 443 8080 8081; do
  firewall-cmd --permanent --add-port=${p}/tcp
done
firewall-cmd --reload

云服务器注意:控制台安全组是第二道墙,本机 firewalld 放行了,安全组不放行同样不通。

第 4 步 · 应用迁移:目录、Jar 上传与启动脚本

从 Windows 迁移过来,两个 Jar 直接 scp/rz 上传到目标目录:

/data/java/app/app.jar
/data/java/admin/admin.jar

每个应用配一个启动脚本 start.sh,这里有两个从 Windows 习惯带过来的经典坑:

# 关闭原有进程
PID=$(ps -ef | grep "app.jar" | grep -v grep | awk '{print $2}')
[ -n "$PID" ] && kill -9 $PID
# 关键:& 必须(否则前台运行脚本永不返回);
#       </dev/null 必须(否则 SSH 会话会被挂住不退出)
nohup java -jar /data/java/app/app.jar > /data/java/app/app.log 2>&1 </dev/null &

Windows 下双击/任务计划运行 jar 从来不关心"会话依赖";Linux 下不加 </dev/null,SSH 一退出进程就可能被挂住或带走。

第 5 步 · systemd 开机自启(替代 Windows 任务计划)

app-java.service / admin-java.service,用 Type=oneshot + RemainAfterExit 套启动脚本,ExecStop 用 pkill -f '^java -jar <路径>' 精确停(防止误杀其他 java 进程):

[Unit]
Description=app Java Jar Service
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/data/java/app/start.sh
ExecStop=/usr/bin/pkill -f '^java -jar /data/java/app/app.jar'
WorkingDirectory=/data/java/app

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable app-java.service admin-java.service

比 Windows 任务计划/rc.local 可靠:开机自动拉起,systemctl status 可查、journalctl -u 可看日志。

第 6 步 · Nginx:Windows 正式配置转换到 Linux

生产 Nginx 配置由 Windows 正式环境的配置原样转换而来,server 块与业务规则保留,改动集中在三处——路径、事件模型、日志:

  1. 路径转换:Windows 盘符/反斜杠路径 → Linux 绝对路径(证书 /etc/nginx/ssl/*.pem、站点根 /var/www/*、日志 /var/log/nginx/);
  2. 事件模型:Windows 的 IOCP → Linux use epoll; + multi_accept on;;
  3. 按日分割日志:用 map $time_iso8601 提取年月日,access_${year}${month}${day}.log 每天一个文件。

多域名 + HTTPS:80 端口统一 301 跳转 HTTPS;每个站点独立 443 server 块(TLS 1.2/1.3、ECDHE 套件、OCSP stapling)。双后端反代对应两个 Java 应用(8080 / 8081):

# 后端①  8080 —— 核心业务 / 充值提现
location /api1/ { proxy_pass http://127.0.0.1:8080; ... }
location [充值提现接口] { proxy_pass http://127.0.0.1:8080; ... }

# 后端②  8081 —— 后台管理 / WebSocket
location /api/ { proxy_pass http://127.0.0.1:8081; ... }

# WebSocket 长连接(聊天推送)
location /ws/ {
    proxy_pass http://127.0.0.1:8081;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

WebSocket 是最容易漏的一条:反代必须显式带 Upgrade/Connection 头并 proxy_http_version 1.1,否则升级握手失败、推送全断。配反代前先查 80 端口,避免与已占用(如 docker)冲突导致绑定失败、reload 静默保留旧配置:

ss -tln | grep -q ':80 ' && echo "80 被占用,先处理" || echo "80 空闲,继续"
nginx -t && systemctl reload nginx

第 7 步 · 验证

systemctl restart app-java admin-java          # 启动两个服务
tail -f /data/java/app/app.log /data/java/admin/admin.log   # 日志确认启动
systemctl is-active nginx && systemctl is-enabled app-java admin-java  # 状态 + 自启

浏览器验证:访问 http://[app域名] 进 APP 页面、http://[admin域名] 进后台页面;同时核对 ss -tlnp 只开放既定端口。

生产 Nginx 配置要点(来自正式环境)

从 Windows 正式环境配置中提取的高价值项,迁移到 Linux 后原样保留:

维度 配置 说明
事件模型 epoll · worker_connections 10240 · multi_accept on Linux 高并发事件驱动
句柄上限 worker_rlimit_nofile 20480 进程级也要放开,只改 limits.conf 无效
大文件上传 client_max_body_size 1000m 保持正式环境 1000M 上传能力
长超时 proxy_read/send_timeout 1800s 充值/提现类接口耗时较长,默认 60s 会断
WebSocket map $http_upgrade $connection_upgrade 聊天推送长连接
限流 limit_req rate=50r/s burst=10 nodelay + limit_conn 单 IP 每秒 50 请求,防刷
黑名单 deny [黑名单IP] 攻击 IP 直接拒绝
隐藏版本 server_tokens off 不暴露 Nginx 版本号
SSL TLSv1.2/1.3 · ECDHE · OCSP stapling 证书需带完整链,否则 stapling 告警
Gzip gzip on level 6 · gzip_vary on 文本 / JS / CSS 压缩
日志 按日分割 + log_format 含 request_body 按日追溯、保留请求体便于排查

迁移经验:这套配置的 server 块与业务规则(反代路径、限流、黑名单)原样搬过来零改动,真正要动的是路径、事件模型、日志这三处"环境相关项"——这正是 Windows → Linux 迁移里"配置翻译"的核心。

Windows → Linux 关键差异(踩坑记录)

# 差异点 Windows 习惯 Linux 处置
1 进程后台化 双击/任务计划,无会话概念 nohup … </dev/null &,& 与 </dev/null 缺一不可
2 JDK 多版本 手动指定 JDK alternatives --set 强制切换 + versionlock 固定
3 句柄上限 桌面无此概念 只改 limits.conf 无效,需 worker_rlimit_nofile + systemd drop-in
4 服务托管 任务计划 / 开机启动文件夹 systemd(oneshot + RemainAfterExit),开机自启 + 可观测
5 端口冲突 常见但难察觉 配反代前先 ss 预检,避免 nginx 绑定失败静默保留旧配置
6 安全层 无防火墙概念 firewalld + 云安全组双层都要放行
7 权限模型 全管理员 SELinux 默认 enforcing 会拦反代/访问,调试期关闭、生产按需启用
8 事件模型 Windows 用 IOCP Linux 配 use epoll; + multi_accept on;
9 WebSocket 反代 配置里没写过 必须 proxy_http_version 1.1 + Upgrade/Connection 头,否则握手失败

迁移前后对照

服务 · 端口 · 归属

  • app.jar —— 8080 —— APP 对外服务(/api1/、[充值提现接口])—— /data/java/app
  • admin.jar —— 8081 —— 后台管理 + WebSocket(/api/、/ws/)—— /data/java/admin
  • nginx —— 80/443 —— 多域名 HTTPS 反代 —— 反代 → 8080 / 8081
  • ssh —— 22 —— 远程管理 —— OpenSSH
  • firewalld —— — — 防火墙 —— 放行 22/80/443/8080/8081

关键处置

  • JDK 8 被 11/17 顶掉 —— alternatives 强制切换 + versionlock 固定
  • SSH 会话挂起 —— nohup 加 & 与 </dev/null
  • nginx 句柄 1024 —— systemd override + worker_rlimit_nofile 65535
  • 80 端口冲突 —— 配反代前 ss 预检
  • WebSocket 握手失败 —— proxy_http_version 1.1 + Upgrade/Connection 头
  • SELinux 拦截 —— 调试期 setenforce 0

验证结果

  • ✅ 两个 Java 应用均上线:app.jar:8080、admin.jar:8081 正常响应;
  • ✅ Nginx 反代通:http://[app域名]、http://[admin域名] 分别命中两个应用;
  • ✅ 开机自启生效:systemctl is-enabled app-java admin-java 均为 enabled;
  • ✅ 端口收敛:对外仅暴露 80/443/22 + 业务端口,与迁移前一致,业务无感切换。

经验与预防

  1. 迁移前先列"差异清单":Windows → Linux 最容易出问题的不是应用本身,而是进程托管、句柄上限、权限、防火墙这些"环境认知差",先逐项对齐再动手;
  2. 步骤固化成幂等脚本 + 手动分步说明双轨:自动执行防漏,分步说明可逐条验证,出问题能精确定位到第几步;
  3. 版本固定必须做:JDK 用 versionlock 钉死,防止 dnf update 顺带升级导致运行环境漂移;
  4. 端口/目录与源保持一致:减少切换期的心智成本,业务侧只认 IP/域名变化;
  5. 先测试机、再目标机:完整走一遍(含真实上传 Jar、起服务、反代),确认无误再上生产。