☰
Linux一键安装JDK/MySQL/Redis/Nginx:脚本化部署与避坑指南
2026/10/29 11:17:48 网站建设 项目流程

简介:面向Linux运维初学者与后端开发者的自动化部署资源包,聚焦在单台Linux服务器上快速搭建Java Web运行环境这一常见需求,将JDK、MySQL、Redis、Nginx四大核心组件的安装、配置与启动流程整合为可复用的脚本方案,省去逐一手动编译与依赖排查的繁琐过程。压缩包共27个文件,以11个sh安装与启动脚本、9个gz源码及依赖包、2个cnf与2个conf配置文件、2个rpm离线依赖包和1个tar归档为主,脚本负责流程编排,源码与rpm保障离线环境可用,配置文件则承载服务参数与系统服务注册,整体约930.56MB。已有298人学习关注。资源按组件分目录组织,涵盖环境变量配置、数据库初始化与安全设置、缓存服务启动、反向代理配置及系统服务管理等环节,并附带依赖组件安装脚本,便于读者理解一键部署的脚本结构与执行顺序,也可作为自建实验环境或内网离线部署的参考模板。

1. 一次装四件套:为什么我坚持用脚本而不是逐条敲命令

上周帮一个刚接手项目的开发者配环境,他花了整整一个下午在四台机器上重复敲 JDK、MySQL、Redis、Nginx 的安装命令,结果第三台机器上 MySQL 的 root 密码策略没对上,第四台机器 Nginx 的编译参数又漏了模块,最后四台机器的环境各不相同,排查问题时连日志路径都要先问一遍。这种场景太常见了——Linux 下装 JDK、MySQL、Redis、Nginx 这套组合,几乎是每个 Java 后端项目的标配,但真正装到「四台机器行为一致、重装能复现」的程度,靠手敲命令基本做不到。一键式安装脚本的价值不在于省那几条命令,而在于把版本、路径、端口、密码策略这些容易漂移的变量固定下来。这篇笔记记录的是我实际落地这套脚本的完整过程:装什么版本、脚本怎么组织、参数怎么改、装完怎么验证,以及装崩了怎么救回来。适合正在配 Linux 开发环境、被多机一致性问题折磨的开发者。

2. 装前定版:四个组件的版本组合与系统前提

2.1 先确认系统底座,再谈组件版本

一键脚本最容易翻车的地方不是脚本本身,而是跑在不匹配的系统上。我一般先做三件事:确认发行版和版本号、确认 CPU 架构、确认是否已有残留的旧版本组件。这三件事决定了后面所有安装包的选型。

# 确认发行版与版本 cat /etc/os-release # 确认架构,x86_64 和 aarch64 的安装包不通用 uname -m # 检查是否已存在旧版本,避免端口和路径冲突 java -version 2>/dev/null mysql --version 2>/dev/null redis-server --version 2>/dev/null nginx -v 2>&1

/etc/os-release里的ID和VERSION_ID是脚本做分支判断的依据,比如 CentOS 7 和 Ubuntu 22.04 在 systemd 服务写法、默认库版本上差异很大。uname -m输出x86_64还是aarch64直接决定下载哪个二进制包,拿错架构的包解压后执行会报cannot execute binary file,这个报错新手很容易误判成权限问题。最后那段检查是给自己留后悔药——如果机器上已经有 MySQL 5.7 在跑,脚本直接覆盖安装大概率会把数据目录搞乱,必须先确认再决定是卸载还是换端口共存。

系统前提我一般卡这几条:glibc 版本不低于 2.17(MySQL 8 和较新 JDK 的硬要求)、磁盘根分区至少留 5GB、内存不低于 2GB(MySQL 8 默认配置在小内存机器上会 OOM)、防火墙和 SELinux 状态心里有数。这些不满足的话,脚本跑得再顺也会在启动阶段挂掉。

2.2 版本组合怎么选:一套经过验证的搭配

四个组件各自有版本兼容矩阵,但一键脚本追求的是「一套组合稳定跑通」,不是每个都追最新。我常用的组合如下,这套搭配在 CentOS 7/8、Ubuntu 20.04/22.04 上都验证过:

组件推荐版本选型理由安装方式
JDK17 LTS长期支持,Spring Boot 3 最低要求,比 8 少踩 TLS 和模块化的坑tar.gz 解压 + 环境变量
MySQL8.0.x默认认证插件、JSON 支持完善,社区资料多官方 tar.xz 二进制包
Redis7.2.x稳定分支,支持 ACL,比 6.x 少一些已知问题源码编译或二进制
Nginx1.24.x 稳定版稳定分支,模块生态成熟源码编译,带常用模块

JDK 选 17 而不是 8,是因为新项目基本都在往 17 迁,而 8 在较新系统上反而容易遇到证书和时区数据的老问题;但如果项目明确锁死 JDK 8,脚本里把版本变量改掉即可,安装逻辑不变。MySQL 用官方二进制包而不是系统源,是为了避开发行版自带的老版本和奇怪的默认配置。Redis 和 Nginx 走源码编译,好处是编译参数可控、安装路径统一,坏处是依赖编译工具链——所以脚本第一步就得把gcc、make、pcre-devel、zlib-devel、openssl-devel这些装齐。

提示:版本号在脚本里全部抽成顶部变量,不要散落在各条命令里。改版本只改一处,这是脚本可维护性的底线。

2.3 目录规划:所有路径先定死

路径混乱是后期维护最大的坑。我习惯把所有组件收在一个统一根目录下,数据、日志、配置、安装文件分开:

# 脚本顶部统一声明,后续所有命令引用变量 INSTALL_ROOT=/opt/soft DATA_ROOT=/data LOG_ROOT=/var/log/app # 目录结构约定 # /opt/soft/jdk17 JDK 安装目录 # /opt/soft/mysql MySQL 安装目录 # /opt/soft/redis Redis 安装目录 # /opt/soft/nginx Nginx 安装目录 # /data/mysql MySQL 数据目录 # /data/redis Redis 数据目录 # /var/log/app/mysql MySQL 日志 # /var/log/app/nginx Nginx 日志

把安装目录和数据目录分开,好处是升级或重装组件时数据不受影响,卸载也干净。/opt/soft放程序、/data放数据、/var/log/app放日志,这个划分在多数生产规范里都成立。脚本里用mkdir -p一次性建好,并给 MySQL 和 Redis 的数据目录单独设属主,避免启动时因为权限被拒。

3. 脚本骨架与 JDK、MySQL 的安装落地

3.1 脚本整体结构:分段执行、失败即停

一键脚本最怕的是「跑到一半挂了,前面改了的东西不知道要不要回滚」。我的做法是脚本开头加严格模式,每个阶段独立成函数,失败立即退出并打印当前阶段:

#!/bin/bash set -euo pipefail # 阶段标记,出错时能定位到哪一步 stage() { echo "===== [$(date '+%H:%M:%S')] $1 ====="; } stage "检查系统环境" check_system() { source /etc/os-release echo "系统: ${ID} ${VERSION_ID}, 架构: $(uname -m)" [ "$(id -u)" -eq 0 ] || { echo "需要 root 权限"; exit 1; } } stage "安装编译依赖" install_deps() { if command -v yum >/dev/null; then yum install -y gcc gcc-c++ make pcre-devel zlib-devel openssl-devel libaio numactl elif command -v apt >/dev/null; then apt update && apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev libaio1 numactl fi } check_system install_deps

set -euo pipefail三个选项分别管:命令失败即退出、引用未定义变量报错、管道中任一环节失败即失败。少了任何一个,脚本都可能在错误状态下继续往下跑,最后装出一个「看起来装好了但起不来」的环境。stage函数是排错时的黑匣子,日志里一眼能看出卡在哪个阶段。依赖安装按包管理器分支,libaio和numactl是 MySQL 二进制的运行时依赖,漏装会在初始化阶段报共享库找不到。

3.2 JDK 17 安装:解压、环境变量、alternatives

JDK 用 tar.gz 解压安装,核心是三件事:解压到固定路径、配全局环境变量、用 alternatives 注册(可选但推荐)。

stage "安装 JDK 17" install_jdk() { local JDK_TAR=/tmp/jdk-17_linux-x64_bin.tar.gz local JDK_DIR=${INSTALL_ROOT}/jdk17 mkdir -p "${INSTALL_ROOT}" tar -xzf "${JDK_TAR}" -C "${INSTALL_ROOT}" # 解压后目录名带版本号,统一软链到 jdk17 ln -sfn "${INSTALL_ROOT}/jdk-17"* "${JDK_DIR}" # 写入全局环境变量,幂等处理避免重复追加 grep -q "JAVA_HOME=${JDK_DIR}" /etc/profile || cat >> /etc/profile <<EOF export JAVA_HOME=${JDK_DIR} export PATH=\$JAVA_HOME/bin:\$PATH EOF # 注册到 alternatives,方便多版本切换 update-alternatives --install /usr/bin/java java "${JDK_DIR}/bin/java" 100 2>/dev/null || true update-alternatives --install /usr/bin/javac javac "${JDK_DIR}/bin/javac" 100 2>/dev/null || true } install_jdk source /etc/profile java -version

解压后目录名通常带完整版本号(如jdk-17.0.9),用通配符加软链统一成jdk17,这样环境变量和后续脚本都不用关心具体小版本。写/etc/profile前先grep判断,是为了脚本可重复执行——第二次跑不会追加重复的export。update-alternatives那段在 Debian 系上有效,RedHat 系没有这个命令,所以加了|| true容错,不影响主流程。最后source后立刻java -version验证,输出里能看到17.x才算这一步过关。

3.3 MySQL 8 安装:初始化、密码策略、服务注册

MySQL 二进制包安装的步骤比 JDK 多,顺序不能乱:解压、建用户、建数据目录、初始化、改配置、注册服务、启动、设密码。

stage "安装 MySQL 8" install_mysql() { local MYSQL_TAR=/tmp/mysql-8.0.36-linux-glibc2.17-x86_64.tar.xz local MYSQL_DIR=${INSTALL_ROOT}/mysql local MYSQL_DATA=${DATA_ROOT}/mysql # 建专用用户,不允许登录 id mysql &>/dev/null || useradd -r -s /sbin/nologin mysql tar -xf "${MYSQL_TAR}" -C "${INSTALL_ROOT}" ln -sfn "${INSTALL_ROOT}/mysql-8.0"* "${MYSQL_DIR}" mkdir -p "${MYSQL_DATA}" "${LOG_ROOT}/mysql" chown -R mysql:mysql "${MYSQL_DATA}" "${LOG_ROOT}/mysql" # 初始化,生成临时 root 密码到日志 "${MYSQL_DIR}/bin/mysqld" --initialize --user=mysql \ --basedir="${MYSQL_DIR}" --datadir="${MYSQL_DATA}" \ --log-error="${LOG_ROOT}/mysql/init.log" # 从初始化日志提取临时密码 local TMP_PWD TMP_PWD=$(grep 'temporary password' "${LOG_ROOT}/mysql/init.log" | awk '{print $NF}') echo "MySQL 临时密码: ${TMP_PWD}" } install_mysql

--initialize和--initialize-insecure的区别要讲清楚:前者生成随机临时密码写进错误日志,后者生成空密码。生产环境一律用前者,空密码初始化等于给自己埋雷。临时密码提取那行依赖日志格式,MySQL 8.0 各小版本措辞略有差异,如果grep不到,直接去init.log里搜temporary password手动确认。数据目录属主必须是mysql,否则初始化会报Can't create/write to file。

初始化完成后写配置文件,重点是端口、字符集、密码策略和日志路径:

# /etc/my.cnf [mysqld] basedir=/opt/soft/mysql datadir=/data/mysql port=3306 socket=/tmp/mysql.sock character-set-server=utf8mb4 collation-server=utf8mb4_0900_ai_ci log-error=/var/log/app/mysql/error.log pid-file=/data/mysql/mysql.pid # 密码策略放宽到 8 位,避免初始化后改密码被拒 validate_password.policy=LOW validate_password.length=8 [client] socket=/tmp/mysql.sock default-character-set=utf8mb4

utf8mb4和utf8mb4_0900_ai_ci是 MySQL 8 的推荐组合,能完整支持四字节字符。密码策略那两行是血泪经验——MySQL 8 默认MEDIUM策略要求大小写数字符号齐全,很多脚本里写死的简单密码会直接改失败,报Your password does not satisfy the current policy requirements。把策略调到LOW并明确长度,脚本里的密码才能稳定生效。服务注册用 systemd:

cat > /etc/systemd/system/mysqld.service <<'EOF' [Unit] Description=MySQL Server After=network.target [Service] User=mysql Group=mysql ExecStart=/opt/soft/mysql/bin/mysqld --defaults-file=/etc/my.cnf LimitNOFILE=65535 Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now mysqld

LimitNOFILE=65535是必须的,MySQL 默认打开文件数限制在部分系统上只有 1024,连接一多就报Too many open files。Restart=on-failure让服务异常退出后自动拉起,配合日志排查。启动后用临时密码登录并立刻改密码:

mysql -uroot -p"${TMP_PWD}" --connect-expired-password \ -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrongPwd123'; FLUSH PRIVILEGES;"

--connect-expired-password不能省,临时密码状态下不带你连改密码的权限都没有。

4. Redis 与 Nginx 的编译安装与参数取舍

4.1 Redis 7 编译安装:从源码到 systemd

Redis 走源码编译,步骤是解压、make、安装到指定前缀、改配置、注册服务。

stage "安装 Redis 7" install_redis() { local REDIS_TAR=/tmp/redis-7.2.4.tar.gz local REDIS_DIR=${INSTALL_ROOT}/redis tar -xzf "${REDIS_TAR}" -C /tmp cd /tmp/redis-7.2.4 make -j"$(nproc)" && make PREFIX="${REDIS_DIR}" install mkdir -p "${DATA_ROOT}/redis" "${LOG_ROOT}/redis" # 配置文件从源码目录拷一份再改 cp redis.conf "${REDIS_DIR}/redis.conf" } install_redis

make -j$(nproc)用满 CPU 核数编译,比单线程快很多。make PREFIX=... install把二进制装到指定目录,不污染系统路径。配置文件从源码目录拷一份再改,比从零写省事,也保留了官方默认项作为参照。关键配置项:

# /opt/soft/redis/redis.conf 关键改动 bind 127.0.0.1 port 6379 daemonize no dir /data/redis logfile /var/log/app/redis/redis.log requirepass YourRedisPwd123 appendonly yes maxmemory 512mb maxmemory-policy allkeys-lru

daemonize no是因为交给 systemd 托管,让 systemd 管进程生命周期比 Redis 自己 daemon 化更可控。appendonly yes开 AOF 持久化,重启不丢数据。maxmemory和淘汰策略必须设,不设的话 Redis 会一直吃内存直到被系统 OOM killer 干掉,这是小内存机器上最常见的翻车点。服务文件:

cat > /etc/systemd/system/redis.service <<'EOF' [Unit] Description=Redis Server After=network.target [Service] User=root ExecStart=/opt/soft/redis/bin/redis-server /opt/soft/redis/redis.conf ExecStop=/opt/soft/redis/bin/redis-cli -a YourRedisPwd123 shutdown Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now redis

ExecStop里带密码调shutdown,否则systemctl stop会因为认证失败停不掉,只能kill -9,AOF 可能损坏。

4.2 Nginx 1.24 编译:模块清单决定后期要不要重装

Nginx 编译安装的核心是./configure的参数,模块选少了后期要加就得重编译,所以一次配齐常用模块:

stage "安装 Nginx 1.24" install_nginx() { local NGINX_TAR=/tmp/nginx-1.24.0.tar.gz local NGINX_DIR=${INSTALL_ROOT}/nginx tar -xzf "${NGINX_TAR}" -C /tmp cd /tmp/nginx-1.24.0 ./configure \ --prefix="${NGINX_DIR}" \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_gzip_static_module \ --with-http_stub_status_module \ --with-stream \ --with-pcre make -j"$(nproc)" && make install } install_nginx

--with-http_ssl_module和--with-http_v2_module是 HTTPS 和 HTTP/2 的前提,默认不编译进去。--with-http_stub_status_module提供状态页,监控用得上。--with-stream支持四层转发,做 TCP 代理时不用再装别的。这些模块如果编译时漏了,后期加只能重新./configure && make,而make install会覆盖二进制,配置和日志不受影响但要重启服务,所以一次配齐最省事。Nginx 的 systemd 服务相对简单:

cat > /etc/systemd/system/nginx.service <<'EOF' [Unit] Description=Nginx Server After=network.target [Service] Type=forking ExecStartPre=/opt/soft/nginx/sbin/nginx -t ExecStart=/opt/soft/nginx/sbin/nginx ExecReload=/opt/soft/nginx/sbin/nginx -s reload ExecStop=/opt/soft/nginx/sbin/nginx -s quit Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now nginx

ExecStartPre先做配置检查,配置有语法错误时服务直接起不来而不是带病运行,这个检查在改完配置重启时特别有用。Type=forking是因为 Nginx 默认以守护进程方式启动,主进程 fork 后父进程退出,systemd 需要这个类型才能正确跟踪。

5. 避坑与排查:脚本跑挂后的五条血泪记录

5.1 现象:MySQL 初始化报 libaio.so.1 找不到

原因:MySQL 二进制包依赖libaio,部分精简系统镜像没预装,--initialize阶段直接报error while loading shared libraries: libaio.so.1。解决:在依赖安装阶段显式加上libaio(CentOS)或libaio1(Ubuntu),不要指望系统自带。我现在的脚本里这条是硬依赖,缺了直接退出并提示。

5.2 现象:脚本第二次执行时环境变量重复追加

原因:往/etc/profile追加export时没做幂等判断,跑几次就有几份重复行,虽然不影响功能但看着乱,且改路径时容易改漏。解决:追加前grep -q判断目标行是否已存在,存在就跳过。所有写配置文件的步骤都应该按这个思路做幂等。

5.3 现象:Redis 启动后立刻退出,日志无报错

原因:daemonize no配了但 systemd 服务里Type没设对,或者dir指向的目录不存在导致启动即失败。解决:先手动前台跑redis-server /opt/soft/redis/redis.conf看真实报错,确认dir目录存在且可写,再检查 systemd 的Type是否为默认simple(前台进程用simple是对的)。日志路径也要确认目录已建,否则日志写不进去,看起来就像「无报错退出」。

5.4 现象:Nginx 启动报端口被占用

原因:系统里已有旧 Nginx 或别的服务占了 80 端口,脚本没检查就直接启动。解决:启动前用ss -lntp | grep :80检查端口,被占用时打印占用进程并退出,让人工决定是停旧服务还是改端口。脚本自动杀进程是危险操作,不做。

5.5 现象:MySQL 改密码报密码策略不满足

原因:MySQL 8 默认validate_password策略为MEDIUM,要求长度、大小写、数字、特殊字符齐全,脚本里写死的密码往往不满足。解决:在my.cnf里把validate_password.policy设为LOW并明确length,或者改密码时用足够复杂的密码。前者更适合一键脚本,因为密码可控。

6. 装完怎么验证:四条命令确认环境真的跑通

脚本跑完不等于环境可用,我每次都会走一遍固定验证流程,四条命令覆盖四个组件,任何一条不过就回去查对应章节。

# 1. JDK:版本号必须是 17.x java -version 2>&1 | head -1 # 2. MySQL:能登录并执行查询 mysql -uroot -p'YourStrongPwd123' -e "SELECT VERSION(); SHOW VARIABLES LIKE 'character_set_server';" # 3. Redis:带密码 ping 返回 PONG,并确认持久化开启 redis-cli -a 'YourRedisPwd123' ping redis-cli -a 'YourRedisPwd123' config get appendonly # 4. Nginx:配置检查通过且能返回状态码 /opt/soft/nginx/sbin/nginx -t curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1

第一条看 JDK 版本是否落在 17,输出里如果还是系统自带的 1.8,说明source /etc/profile没生效或alternatives优先级被覆盖。第二条除了版本,我特意查了character_set_server,确认是utf8mb4,字符集不对后期存中文会出乱码,属于「装的时候不觉得、用起来才炸」的问题。第三条ping验证连通和认证,config get appendonly确认持久化真的开了——配置文件里写了不代表加载了,Redis 启动时如果配置文件路径传错,会静默用默认配置跑。第四条nginx -t验证配置语法,curl拿状态码验证服务真的在监听,返回200或403都算正常(403是默认没有 index 文件,服务本身是通的)。

再补一个跨组件的连通性检查:从 MySQL 里建个库、Redis 里写个键、Nginx 配个反代指向后端,确认四件套之间能协同。这一步能暴露端口、防火墙、SELinux 层面的问题,比单组件验证更接近真实使用。

注意:验证用的密码和脚本里写死的密码要一致,改过密码后记得同步更新验证命令,否则会误判成服务没起来。

从那以后我每次跑完一键脚本,都强制走一遍这四条验证加一次跨组件连通,不看到四个组件的真实输出不算装完。脚本能省掉重复劳动,但省不掉验证这一步——装得快和装得对是两件事,后者只能靠亲手确认。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询