我第一次独立在Linux服务器上部署Java服务的时候,想法特别简单:把jar包扔上去,java -jar跑起来就完事。结果那天晚上十一点,我坐在地上对着终端发呆,MySQL连不上、8080端口被占、进程起来三秒就挂,三个问题排着队来找我。后来我才意识到,部署这件事从来不是“把包传上去启动”这么简单。它背后是一整套链路:运行环境是否干净、网络是否可达、进程挂了谁能拉起、日志去哪看、升级怎么不停服、安全怎么守住。任何一个环节没想过,线上就会用各种诡异的方式给你上课。
这篇文章我会按自己实际部署的完整路径,把Linux + Java服务从零跑到稳定运行的整个流程拆开讲清楚。包括服务器基础准备、JDK选型、打包上传、systemd和Docker两种启动方式、部署后的体检清单、几个高频故障的真实排查链路,以及升级回滚和安全加固的实操经验。不管你是刚接触部署的Java开发,还是被临时拉去管服务器的运维新人,这篇文章都可以当作一份可以直接照着做的操作手册。
1. 先理清部署这件事的三层问题:运行环境、可访问性、故障恢复
很多人部署失败,不是某个命令敲错了,而是脑子里缺少一张完整的链路图。我习惯把一次部署拆成三个问题来看。
1.1 环境问题:你的服务依赖什么,它们都在哪
Java服务几乎不可能孤零零跑起来。一个典型的Spring Boot单体项目,背后通常站着MySQL、Redis、Nginx这些基础设施。微服务项目就更复杂,还有注册中心、配置中心、消息队列。第一步不是急着装JDK,而是先盘清楚你的服务到底依赖哪些组件。
我建议在动手之前画一张最朴素的依赖清单,不用画花哨的架构图,列个表格就行:
| 组件 | 用途 | 端口 | 是否需要公网暴露 |
|---|---|---|---|
| JDK | Java运行环境 | 无 | 否 |
| MySQL | 业务数据存储 | 3306 | 否,仅内网 |
| Redis | 缓存/会话 | 6379 | 否,仅内网 |
| Nginx | 反向代理/静态资源 | 80/443 | 是 |
| 业务服务 | 核心API | 8080 | 按需 |
这张表的作用是让你知道:哪些组件需要从外部访问,哪些只要内网能通就行。很多部署事故都是因为把数据库端口暴露到了公网,或者反过来,服务起了、端口也监听了,但安全组没放行,外部怎么都访问不到。
1.2 可访问性问题:进程活着不等于服务可用
这是新手最容易混淆的一点。java -jar启动后终端显示“Started Application in 5.2 seconds”,你以为大功告成,结果浏览器访问不了。原因通常出在三个层面:
- 应用监听地址不对:Spring Boot默认监听
0.0.0.0,但如果有人改了server.address=127.0.0.1,外部就永远访问不到。 - Linux防火墙拦截:
firewalld或ufw没有放行业务端口。 - 云平台安全组没放行:阿里云、腾讯云这些都有独立于系统防火墙的访问控制层,Linux防火墙放行不够,或者放行了但安全组没开,都会被挡在外面。
排查这类问题,要按照“网线是否通→防火墙是否放行→系统端口是否监听→应用是否正常响应”的顺序一层层看,不要一上来就怀疑代码。
1.3 故障恢复:进程挂了你知不知道,谁来拉起
裸奔的Java进程,终端一关就没了,或者OOM被系统杀掉就再也起不来。部署之前必须先定好这几点:
- 谁来拉起进程(systemd、Docker的restart策略、还是手工脚本)
- 进程挂了你知不知道(有没有健康检查脚本或监控告警)
- 日志写到哪(
journald、文件、还是容器stdout)
这三点就是服务自愈能力的地基。我在后面的章节会分别展开讲systemd和Docker的配置方法,这里先把这个心智模型立起来:部署不是一次性的动作,而是一套让你的服务能持续对外提供服务的机制。
2. 从零准备一台能跑Java服务的Linux主机
2.1 系统选择与初始化操作
大部分云厂商都提供Ubuntu、Debian、CentOS这些主流发行版镜像。我自己的倾向是:新项目用Ubuntu 22.04 LTS或者Debian 12,CentOS 7已经停止维护很久了,CentOS Stream的定位也不太适合追求稳定的生产环境。
用root登录后的第一件事,是按顺序做基础初始化:
# 查看系统版本,确认自己没登错机器 cat /etc/os-release # Debian/Ubuntu系 apt update && apt upgrade -y # 安装基础工具 apt install -y curl wget vim git lsof net-tools unzip tree这里有个容易被忽略的细节:lsof和net-tools(提供netstat)这些命令在最小化安装的系统里默认没有,真到排查端口问题的时候再装就手忙脚乱了。所以提前装好,备着垫底。
2.2 创建部署用户,别用root跑服务
我一直坚持用独立的账号跑Java服务,这个习惯救过我很多次。原因很直白:应用一旦被攻破,root权限意味着整个服务器沦陷;而且不同项目混在root下,文件权限、日志、环境变量全搅在一起,维护成本极高。
创建用户的命令很简单:
# 创建deploy用户,并加入sudo组 useradd -m -s /bin/bash deploy usermod -aG sudo deploy # 设置密码 passwd deploy后面所有JDK安装、项目部署、服务启动,都用deploy用户来做。目录所有权也要对应给好:
mkdir -p /opt/app chown -R deploy:deploy /opt/app2.3 SSH密钥登录配置
密码登录在暴力破解面前就是一层纸,特别是22端口暴露在公网上的情况。我之前看过服务器日志,一天几万次失败登录尝试是常态。所以密钥登录不是可选项,是必选项。
在本地电脑上生成密钥并拷贝到服务器:
# 本地执行 ssh-keygen -t ed25519 -C "deploy@your-machine" ssh-copy-id deploy@你的服务器IP拷完之后,建议先不要急着关密码登录,等新会话确认密钥能登进去再改配置。改/etc/ssh/sshd_config:
PasswordAuthentication no PubkeyAuthentication yes然后systemctl restart sshd。注意:一定要保留一个已登录的会话窗口再重启SSH,防止配置写错把自己锁在外面。
2.4 云安全组与系统防火墙
云服务器的访问控制是两层:Linux系统防火墙(iptables/firewalld/ufw)+ 云平台安全组。两层都要放行端口才能通。我见过太多人只改了一层,另一层忘记改,结果服务起在服务器上看不到任何问题,外面就是连不上。
在Ubuntu上推荐直接关闭ufw或明确放行端口,二选一,不要模棱两可:
# 方式一:明确放行必要端口 ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw allow 8080/tcp ufw enable # 方式二:如果暂时不想启用ufw,至少确认状态 ufw status云平台安全组那边,尽量只放行22、80、443和业务端口。数据库3306、Redis 6379这些内部端口,永远不要直接暴露到公网,至少加个来源IP白名单。
3. JDK选型与环境变量:为什么很多人卡在部署第一步
3.1 JDK版本怎么选:8、11、17还是21
版本选择不是越新越好,要看你项目的底层框架。
| JDK版本 | 对Java服务的影响 | 我的建议 |
|---|---|---|
| JDK 8 | 老项目绝对主力,Spring Boot 2.x兼容性最好 | 老项目不要轻易升 |
| JDK 11 | 已有部分机构使用,但生态定位比较尴尬 | 新项目可以避开的中间版本 |
| JDK 17 | 当前Spring Boot 3.x的最佳搭配,长期支持版本 | 新项目首选 |
| JDK 21 | 更新的LTS,虚拟线程等特性亮眼 | 技术栈较新的团队可以上 |
最经典的翻车场景是:本地用JDK 17编译,服务器装的是JDK 8,启动直接报UnsupportedClassVersionError。这个错误码的意思是“编译用的JDK比运行JDK版本高”,不是代码问题,是环境不一致。所以先问清楚项目是用什么版本编译的,再决定服务器装什么,或者反过来,服务器装了17,本地也统一用17。
3.2 安装JDK的三种方式对比
- 包管理器安装:
apt install openjdk-17-jdk-headless,最省事,适合大多数时候。但官方源里的JDK版本可能滞后。 - 二进制包安装:自己去网上下载tar.gz包放到
/opt下,解压即用,版本完全可控。对需要精确定制JDK版本的场景推荐。 - 容器镜像:
eclipse-temurin:17-jre这类镜像,适合Docker部署,后面第5章会讲。
二进制包安装的完整操作:
tar -xzf jdk-17_linux-x64_bin.tar.gz -C /opt/ mv /opt/jdk-17.0.10 /opt/jdk17 # 写环境变量 cat > /etc/profile.d/java.sh << 'EOF' export JAVA_HOME=/opt/jdk17 export PATH=$JAVA_HOME/bin:$PATH EOF # 让环境变量立即生效并验证 source /etc/profile.d/java.sh java -version写环境变量到/etc/profile.d/而不是直接改/etc/profile,这个习惯很重要。前者按文件拆开管理,升级JDK时只需要改一个文件,也方便一眼看清这台机器装了什么。
3.3 JVM启动参数不能随便给
下面这套参数是我实测下来比较稳的基准配置,适合大多数Spring Boot服务:
java -Xms512m -Xmx512m \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=256m \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/data/logs/heapdump.hprof \ -Dfile.encoding=UTF-8 \ -Dspring.profiles.active=prod \ -jar app.jar# 更推荐方式:写成易读的列表格式 java \ -Xms512m \ -Xmx512m \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=256m \ -Dfile.encoding=UTF-8 \ -Dspring.profiles.active=prod \ -jar /opt/app/demo.jar几个值得注意的点:
-Xms和-Xmx设置成相同值,避免JVM运行中动态扩容缩容带来的性能抖动。-Xmx不是越大越好。服务器物理内存假设4G,你给JVM 3G,一旦发生内存尖峰,系统可能触发OOM Killer直接把进程干掉。留30%-40%内存给操作系统和文件缓存才是合理的。-Dfile.encoding=UTF-8能解决一大部分中文乱码问题,尤其是日志里中文变???的情况。HeapDumpOnOutOfMemoryError这个参数平时不起眼,但线上OOM时,它生成的堆转储文件是你分析内存泄漏的唯一线索。
4. 打包、上传与服务目录规划:把代码放到正确的位置
4.1 Maven打包:为什么本地能跑服务器却不行
先把本地打包流程跑标准:
mvn clean package -DskipTests打包完去target/目录找产物。Spring Boot项目会有两个jar:一个以-plain.jar结尾,一个以项目名结尾。要启动的是那个带完整Spring Boot插件重打包过的jar,不是-plain.jar。这两个jar的区别在于是否包含内嵌Tomcat和所有依赖。如果你启动时频繁报no main manifest attribute,基本就是选错了jar,或者项目没有配置spring-boot-maven-plugin。
<!-- pom.xml 中必须包含 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>4.2 上传方式:scp、rsync还是Git拉取
- scp:最常用。包不大、频率不高时用它就够。
- rsync:大包或者频繁更新时推荐,只传增量部分。
- Git拉取+服务器上构建:适合源码部署的场景,但是生产环境不建议装JDK之外的编译工具链,Java项目还是本地构建完传jar包更干净。
# 本地构建后上传到服务器 scp target/demo.jar deploy@你的服务器IP:/opt/app/demo.jar # 或者用rsync,支持断点续传和增量 rsync -avz --progress target/demo.jar deploy@你的服务器IP:/opt/app/demo.jar4.3 目录规划:别把东西都堆在/root或者/home
我自己的目录规范如下:
| 路径 | 用途 | 说明 |
|---|---|---|
/opt/app/{项目名}/lib | 存放jar包 | 按项目隔离 |
/opt/app/{项目名}/config | 外部配置文件 | 环境差异大的配置放这 |
/data/logs/{项目名}/ | 运行日志 | 单独挂盘最好 |
/data/backup/{项目名}/{日期}/ | 升级前的备份 | 至少保留3份 |
为什么要分这么细?两个原因:
第一,配置和代码分离。jar包里的application.yml是给默认环境跑的,生产环境的数据库密码、第三方密钥应该通过外部的application-prod.yml或环境变量注入。这样升级时只替换jar,不需要重新修改配置。
第二,日志单独放盘。日志文件增长速度远超你的想象,如果/根分区被日志打满,整个系统都会出问题。单独分一个/data挂载点,出问题时清理日志不影响系统分区。
5. 启动方式对比:nohup、systemd与Docker我建议怎么选
5.1 nohup启动:最快但不是终点
最后层级的启动命令:
nohup java -jar /opt/app/demo.jar > /data/logs/demo/console.log 2>&1 &拆开解释一下:
nohup:忽略挂断信号,终端关了进程不会跟着死。> file 2>&1:把标准输出和错误输出都重定向到日志文件。&:放到后台执行。
这套命令能让你快速验证服务能否跑起来,但它最大的问题是:进程挂了没人管,重启靠人工记忆。我自己的经验是,它只适合本地调试,不适合生产。
5.2 systemd:单机部署的首选方案
在生产环境,我个人最推荐systemd。它足够原生,每一个Linux运维都看得懂,而且自带守护、开机自启、日志统一管理。
新建文件/etc/systemd/system/demo.service:
[Unit] Description=Java Demo Service After=network.target [Service] Type=simple User=deploy Group=deploy WorkingDirectory=/opt/app/demo EnvironmentFile=/etc/demo.env ExecStart=/opt/jdk17/bin/java -Xms512m -Xmx512m -Dspring.profiles.active=prod -jar /opt/app/demo/demo.jar Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target几个关键配置项的作用:
Restart=always:进程崩溃、被kill、系统重启后都会自动拉起,这是服务自愈的基础。RestartSec=5:重启前等5秒,防止故障时疯狂重启刷日志。EnvironmentFile:把数据库密码、第三方密钥放到/etc/demo.env这个独立文件,不写死在unit文件里,权限设成600。User=deploy:强制用普通用户运行,不用root。
操作命令:
systemctl daemon-reload systemctl enable --now demo systemctl status demo # 查看日志,-u 指定服务,-f 跟随 journalctl -u demo -f5.3 Docker部署:环境隔离,适合服务多的场景
服务多起来之后,systemd管理一堆进程也会乱。这时候Docker的价值就出来了:环境一致性、依赖隔离、重启策略内置。
Dockerfile示例:
FROM eclipse-temurin:17-jre WORKDIR /app RUN useradd -r -s /sbin/nologin appuser COPY target/demo.jar app.jar COPY app-prod.yml /app/config/app-prod.yml USER appuser ENV SPRING_PROFILES_ACTIVE=prod ENTRYPOINT ["java", "-Xms512m", "-Xmx512m", "-jar", "app.jar"]docker-compose.yml示例:
version: '3.8' services: demo: build: . container_name: demo ports: - "8080:8080" environment: SPRING_PROFILES_ACTIVE: prod env_file: - demo.env volumes: - /data/logs/demo:/app/logs restart: unless-stoppedrestart: unless-stopped对应systemd的Restart=always,是容器自愈的关键。
三种方式的对比:
| 维度 | nohup | systemd | Docker |
|---|---|---|---|
| 自动重启 | 不支持 | 内置 | 内置 |
| 开机自启 | 不支持 | 支持 | 需要额外设置 |
| 日志管理 | 重定向到文件 | journald统一管理 | stdout收集 |
| 环境隔离 | 无 | 无 | 完全隔离 |
| 学习成本 | 最低 | 中 | 中高 |
我的选择逻辑可以复制:单机单服务,用systemd;服务数量多或者对运行环境要求差异大,用Docker;Kubernetes是后面规模化的事,不建议小项目一步到位。
6. 部署后的第一轮体检:进程、端口、日志与健康检查
服务启动完,别急着下班,先做完下面这一轮体检。
6.1 确认进程和端口状态
# 查看Java进程 jps -l ps -ef | grep java | grep -v grep # 查看端口监听 ss -lntp | grep 8080 lsof -i:8080重点看两点:
- 进程是否还在(很多服务启动失败后进程直接消失)
- 端口监听地址是
0.0.0.0还是127.0.0.1,后者意味着只能本机访问
6.2 健康接口与错误日志
Spring Boot项目推荐引入spring-boot-starter-actuator并暴露健康检查端点:
curl -s http://127.0.0.1:8080/actuator/health # 期望输出 {"status":"UP"}用127.0.0.1而不是公网IP做探测,是为了验证“本机服务正常”,然后你再去安全组和防火墙排查外部网络的问题。这一步能把“服务本身故障”和“网络不通”分离开。
日志检查:
# 实时查看日志 tail -f /data/logs/demo/app.log # 启动日志里的异常 grep -E "ERROR|Exception|Caused by" /data/logs/demo/app.log | head -506.3 一张可以直接照抄的体检清单
我每部署一个服务,都会把这个脚本跑一遍:
echo "=== 1. 进程检查 ===" ps -ef | grep demo.jar | grep -v grep || echo "FAIL: 进程不存在" echo "=== 2. 端口检查 ===" ss -lntp | grep 8080 || echo "FAIL: 8080未监听" echo "=== 3. 健康检查 ===" curl -s --max-time 5 http://127.0.0.1:8080/actuator/health || echo "FAIL: 健康接口无响应" echo "=== 4. 系统资源 ===" free -h df -h /这四步跑完,服务“活没活着、能不能接客、环境还剩多少余地”基本就清楚了。剩下的事情就是把这些命令存成healthcheck.sh,以后每次部署完只跑一次这个脚本。
7. 部署一定绕不开的坑:数据库连接、内存不足与端口冲突
7.1 MySQL连接不上的真相:密码插件与网络白名单
现象:项目启动报Communications link failure,或者Access denied for user。
排查链路:
# 第一步:确认网络通不通,端口通不通 telnet 数据库内网IP 3306 # 如果telnet不通,问题在防火墙/安全组/网络,不关代码的事 # 第二步:确认账号能不能连 mysql -h 数据库内网IP -u app_user -p根因:最常见的是两个,一是MySQL 8默认的caching_sha2_password认证插件和旧版本JDBC驱动不兼容,二是数据库账号只允许localhost访问,没有授权给应用服务器的内网IP。
解决:
-- 在MySQL上执行为应用账号授权,允许从应用服务器网段访问 CREATE USER 'app_user'@'10.0.0.%' IDENTIFIED BY '强密码'; GRANT SELECT, INSERT, UPDATE, DELETE ON demo_db.* TO 'app_user'@'10.0.0.%'; FLUSH PRIVILEGES;如果是密码插件问题,检查JDBC驱动版本,MySQL 8.0.29之后的版本对caching_sha2_password支持已经很好,优先升级驱动而不是回退认证方式。
7.2 Java进程三秒就挂:内存不足和OOM Killing
现象:systemctl status demo显示进程启动后立刻消失,或者过一段时间消失。
排查链路:
# 第一步:看系统日志有没有OOM记录 dmesg | grep -i "out of memory" | tail -20 dmesg | egrep -i "killed process" | tail -20 # 第二步:看服务器剩余的物理内存 free -h根因:服务器物理内存不足,Linux内核的OOM Killer会根据评分杀掉最“占内存”的进程,Java服务往往是头号目标。我处理过一台4G内存的机器,部署了3个Java服务,每个都设置-Xmx2g,结果服务轮流被杀,就是典型的超卖。
解决:把-Xmx压到合理范围,或者加物理内存。给Linux内核留出的余量至少30%。另外可以用/etc/systemd/system.conf里的DefaultLimitNOFILE等参数配合调优,但不是关键路径。
7.3 端口被占用:启动日志让一切现形
现象:Web server failed to start. Port 8080 was already in use.
排查链路:
lsof -i:8080 # 或 ss -lntp | grep 8080看到PID后,用ps -ef | grep PID确认是哪个进程,然后决定是杀掉它还是换端口:
kill -15 PID注意不要一上来就kill -9,先给进程优雅退出的机会。实在不行再升级到-9。
7.4 日志中文乱码:编码问题不是玄学
现象:日志里的中文变成???或者乱码。
排查链路:
# 查看系统语言环境 locale # 查看JVM文件编码 java -XshowSettings:property -version 2>&1 | grep file.encoding解决:JVM启动参数加-Dfile.encoding=UTF-8,同时确保日志配置文件(Logback/Log4j2)里指定了UTF-8字符集。数据库连接串也要加上characterEncoding=utf8。
7.5 一个通用的故障排查顺序
踩了这么多坑,我总结出最有效的排查套路是分层定位,不要跳跃:
- 先看进程:进程还在不在?不在就去看
dmesg或者systemd状态。 - 再看端口:在的话,端口有没有监听?监听在哪个网卡?
- 然后本机访问:
curl 127.0.0.1:端口通不通?不通去看日志。 - 最后外部访问:本机通但外部不通,问题在防火墙/安全组。
这个顺序能帮你避免90%的无效排查。很多新手一上来就怀疑代码,结果反过来查了半天,最后发现是安全组端口没放行。
8. 升级与重启:从kill -9到优雅停机的正确姿势
8.1 升级前先备份,这是底线动作
不管是修bug还是发新功能,升级之前先备份:
# 备份旧包 mkdir -p /data/backup/demo/$(date +%Y%m%d) cp /opt/app/demo/demo.jar /data/backup/demo/$(date +%Y%m%d)/ # 备份数据库(MySQL示例) mysqldump -u root -p demo_db > /data/backup/demo/$(date +%Y%m%d)/demo_db.sql备份的意义不是做做样子,而是让你在升级出问题时可以毫无心理负担地回滚,不用临时找同事要旧包。
8.2 优雅停机:kill -9是最后手段
不少人重启服务直接用kill -9 PID,这是最粗暴的方式。Java服务正在处理请求,kill -9会直接中断进行中的事务,可能导致数据不一致。正确的姿势是:
# systemd方式 systemctl stop demo # 或 systemctl restart demo # 手动方式,先发TERM信号 kill -15 PIDSpring Boot 2.3+支持优雅停机:
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s这样配置后,服务收到终止信号,不会再接收新请求,已有请求处理完(最多等30秒)再退出。MySQL连接池、Redis连接都会正常释放。
8.3 不停服升级的简化版方案
单机场景怎么做到尽量不停服?用Nginx做代理,配合多实例和软链接切换。
一个简化的发布脚本思路:
#!/usr/bin/env bash # deploy.sh - 简化版蓝绿切换 APP_NAME=demo JAR_DIR=/opt/app/${APP_NAME} BACKUP_DIR=/data/backup/${APP_NAME}/$(date +%Y%m%d%H%M%S) # 1. 备份当前版本 mkdir -p ${BACKUP_DIR} cp ${JAR_DIR}/demo.jar ${BACKUP_DIR}/ # 2. 将新jar放到新目录 mkdir -p ${JAR_DIR}/release cp /tmp/demo.jar ${JAR_DIR}/release/demo.jar # 3. 切换软链接 ln -sfn ${JAR_DIR}/release ${JAR_DIR}/active # 4. 重启服务 systemctl restart ${APP_NAME} # 5. 等待并检查健康状态 sleep 15 if curl -s --max-time 5 http://127.0.0.1:8080/actuator/health | grep -q '"status":"UP"'; then echo "部署成功" else echo "部署失败,回滚到上一版本" systemctl stop ${APP_NAME} ln -sfn ${BACKUP_DIR} ${JAR_DIR}/active systemctl start ${APP_NAME} exit 1 fi多实例场景下,用Nginx upstream管理节点权重,先摘掉一台的流量(weight=0),部署完再切回来,一台一台滚动升级,几乎不影响用户体验。这个思路比蓝绿发布简单,但同样有效。
9. 安全加固三板斧:非root用户、防火墙与密钥登录
9.1 应用服务不允许用root跑
用root跑java -jar可能在开发时给你省事,但生产环境风险极大:应用如果被打穿,攻击者直接拿到root权限,你的服务器就完全沦陷了。正确做法是前面建好的deploy用户。
同时注意文件权限:
# 应用目录deploy用户读写 chown -R deploy:deploy /opt/app/demo # 配置目录只读 chmod -R 750 /opt/app/demo/config # 环境变量文件只有deploy和root可读 chmod 600 /etc/demo.env9.2 防火墙最小开放原则
只开放必要端口,其余一律关闭。我在第2章已经给了ufw的配置示例,这里补充一个常见误区:很多人配置防火墙时只写了“开放80端口”,却忘了给业务端口放行,结果Nginx能访问,后面的Java服务一直报502。放行端口时按实际需要来,宁少勿多。
数据库安全是重点:MySQL的3306端口,绑定地址改成内网IP,不要监听0.0.0.0。云安全组层面,来源IP只填应用服务器的内网IP。应用账号永远不要用root连接数据库,只给必要的增删改查权限。
9.3 SSH关闭密码登录,并记录一份连接清单
密钥登录配置我前面已经写过了。这里再补充一句:改SSH配置之前,先开一个新终端测试密钥能否登录。很多人改完直接断开当前会话,发现密钥没拷好,人就彻底进不去了。
最后一个小建议:把服务器上运行了哪些服务、端口是什么、对应哪个应用、用什么账号跑的,整理成一份简单的文档放内网。平时感觉没用,出故障的时候这份文档能帮你节省至少半小时的定位时间。我把这份清单叫做“服务器台账”,每台机器维护一份,越简单越好,但一定要有。