一、前言
2026 年 5 月,安全研究机构 DepthFirst 披露 Nginx 高危漏洞 CVE-2026-42945(NGINX Rift),该漏洞为堆缓冲区溢出,CVSS 评分 9.2(Critical),影响 Nginx 0.6.27 ~ 1.30.0 全版本。攻击者可利用该漏洞实现远程代码执行(RCE)或拒绝服务(DoS),线上环境存在严重安全风险。
当前业务使用版本处于漏洞影响范围,为彻底修复该高危漏洞,本次将 Nginx 平滑升级至 1.30.1 官方修复版本。采用二进制热升级方式,全程业务无中断,保留完整备份与回滚方案,保障业务连续运行。
二、前置说明
1、操作窗口:业务低峰期执行;
2、升级范围:仅 Nginx 主程序,不改动任何业务配置、第三方模块、运行环境;
3、核心原则:复用原有全部编译参数、运行用户、目录结构;
4、兜底方案:全目录备份 + 旧二进制备份,支持秒级回滚。
三、手动升级步骤
1、全量备份
# 备份 Nginx 整目录(配置、日志、页面、二进制)
cp -a /data/nginx /data/nginx_bak_$(date +%F_%H%M%S)
# 备份当前版本及编译参数
/data/nginx/sbin/nginx -V > /data/nginx/nginx_version_bak.txt 2>&1
2、下载 Nginx 1.30.1 源码
cd /opt
[ ! -f nginx-1.30.1.tar.gz ] && wget https://nginx.org/download/nginx-1.30.1.tar.gz
3、提取原有编译参数
cd /data/nginx/sbin
#查看nginx版本
./nginx -v
#查看nginx编译安装的模块
./nginx -V
#保存好编译参数,更新时要用到(configure arguments:后面的)
–user.....
4、编译安装nginx
#安装依赖
yum install -y pcre pcre-devel gcc-c++ lua-devel pcre pcre-devel zlib zlib-devel openssl openssl-devel wget unzip
#进入存放安装包目录并解压
cd /opt
[ ! -d nginx-1.30.1 ] && tar -zxvf nginx-1.30.1.tar.gz
#检查环境设置编译参数(编译参数用以前的但目录要改成新的)
cd nginx-1.30.1
./configure –user......
#编译二进制文件但不进行安装
make
5、 替换线上二进制文件
cd /data/nginx/sbin
# 备份旧二进制
mv nginx nginx.old.$(date +%F_%H%M%S)
# 拷贝新编译程序
/bin/cp /server/tools/nginx-1.30.1/objs/nginx ./
chmod +x nginx
# 校验版本 + 配置语法
./nginx -v
./nginx -t
6、统一 HTTP2 配置语法(可选)
# 查找文件中是否存在http2
cd /data/nginx/conf
grep -r -B 2 -A 4 "http2" ./*
# 如果没有,不需要执行下面命令(1和2)
# 1)把 listen 443 ssl http2 替换成 listen 443 ssl(根据文件所在位置执行)
sed -i 's/listen 443 ssl http2;/listen 443 ssl;/g' ./*.conf
sed -i 's/listen 443 ssl http2;/listen 443 ssl;/g' ./vhost/*.conf
# 2)在每个 listen 443 ssl 下面添加 http2 on;
sed -i '/listen 443 ssl;/a \\ http2 on;' ./*.conf
sed -i 's/listen 443 ssl http2;/listen 443 ssl;/g' ./vhost/*.conf
# 3)验证
grep -r -B 2 -A 4 "http2" ./*
7、平滑热升级(核心,业务零中断)
7.1 获取主进程 PID
# 获取 Nginx Master 主进程 PID,这个需要确认下
OLD_MASTER_PID=$(ps -eo pid,args | grep 'nginx: master' | grep -v grep | awk '{print $1}')
echo "原 Master PID: ${OLD_MASTER_PID}"
7.2 发送 USR2 信号,拉起新版 Nginx
旧 Master 会启动一组新版 Nginx 进程,新旧进程共存、同时对外提供服务
kill -USR2 ${OLD_MASTER_PID}
# 查看进程,此时会出现两组 master + worker
ps aux | grep nginx | grep -v grep
7.3 旧进程停止接收新连接(流量逐步切至新版)
kill -WINCH ${OLD_MASTER_PID}
7.4 业务观测(必做,观测 5~15 分钟)
检查项:页面访问、接口请求、HTTPS、HTTP2、日志报错、连接数、业务功能、FastDFS 上传下载。
确认全部正常后再执行下一步。
7.5 优雅下线旧 Master 进程
注意:因为nginx版本跨度过大,这个操作很可能会存在两个master同时退出的情况,强烈要求在业务量非常小的情况下,执行,如果失败可以用systemctl start nginx恢复
kill -QUIT ${OLD_MASTER_PID}
# 最终校验,仅保留新版进程
CUR_MASTER_PID=$(ps -eo pid,args | grep 'nginx: master' | grep -v grep | awk '{print $1}')
RUN_BIN=$(readlink -f /proc/${CUR_MASTER_PID}/exe)
ps aux | grep nginx | grep -v grep
${RUN_BIN} -V
四、应急回滚步骤(升级异常立即执行)
注意:此回滚操作是基于两个master进程还同时存在时,如果已经执行了步骤7.5则直接从下面第5个步骤开始操作
# 1. 获取新旧主进程 PID
OLD_PID=$(ps -eo pid,args | grep 'nginx: master' | grep -v grep | head -n1 | awk '{print $1}')
NEW_PID=$(ps -eo pid,args | grep 'nginx: master' | grep -v grep | tail -n1 | awk '{print $1}')
# 2. 新版停止接收新流量
kill -WINCH ${NEW_PID}
# 3. 恢复旧版本流量
kill -CONT ${OLD_PID}
# 4. 关闭新版进程
kill -QUIT ${NEW_PID}
# 5. 恢复旧二进制(下面的*是基于只有一个nginx.old)
cd /data/nginx/sbin
mv nginx nginx.error
mv nginx.old.* nginx
chmod +x nginx
# 6. 重载配置
/data/nginx/sbin/nginx -t
/data/nginx/sbin/nginx -s reload
五、Nginx版本升级脚本
执行这个脚本,选择下线旧进程后,因为nginx版本跨度过大,这个操作很可能会存在两个master同时退出的情况,强烈要求在业务量非常小的情况下,执行,如果失败脚本会尝试用systemctl start nginx恢复。
#vim ~/script/nginx_upgrade_1301.sh
#!/bin/bash
# Nginx升级脚本 1.30.1
# 修复漏洞 CVE-2026-42945 | 无 Lua/NDK 等第三方模块
# 业务平滑热升级 + 自动备份 + 日志记录 + 异常退出
set -eo pipefail
# ==================== 全局变量 ====================
PACKAGE_PATH="/opt"
NGINX_HOME="/data/nginx"
NGINX_BIN="${NGINX_HOME}/sbin/nginx"
NGINX_CONF="${NGINX_HOME}/conf"
BACKUP_DIR="${NGINX_HOME}_bak_$(date +%F_%H%M%S)"
LOG_FILE="/var/log/nginx_upgrade_$(date +%F).log"
NGINX_TAR="nginx-1.30.1.tar.gz"
NGINX_SRC_DIR="${PACKAGE_PATH}/nginx-1.30.1"
RUN_USER="work"
# ===================================================
# 日志输出
log() {
echo "[$(date +%Y-%m-%d\\ %H:%M:%S)] $1" | tee -a "${LOG_FILE}"
}
# 错误退出
error_exit() {
log "ERROR: $1"
exit 1
}
# 安全获取nginx master pid数组,规避pipefail导致脚本直接退出
get_nginx_master_pids() {
ps -eo pid,cmd 2>/dev/null | awk '/nginx: master/ {print $1}' || true
}
# 校验PID对应的进程是否真实存活
is_pid_alive() {
local pid="$1"
[[ -n "${pid}" && -d "/proc/${pid}" ]]
}
# ==================== 交互确认 ====================
clear
echo "========================================="
echo " Nginx 平滑升级至 1.30.1 安全版 "
echo " 修复 CVE-2026-42945 高危漏洞 "
echo "========================================="
read -p "当前为业务低峰期,确认开始升级?(y/n) " CHOICE
[[ ${CHOICE} != [Yy] ]] && log "用户取消升级,脚本退出" && exit 0
# ==================== 1. 环境校验(仅检查存在,不锁定PID用于后续热升级) ====================
log "===== 1. 环境检查 ====="
# 仅校验是否存在 Nginx master,不长期保存该PID
INIT_PID_LIST=($(get_nginx_master_pids))
VALID_INIT_PIDS=()
for pid in "${INIT_PID_LIST[@]}"; do
if is_pid_alive "${pid}"; then
VALID_INIT_PIDS+=("${pid}")
fi
done
if [[ ${#VALID_INIT_PIDS[@]} -eq 0 ]]; then
error_exit "未检测到 Nginx Master 进程,请先启动原有 Nginx 再执行"
fi
log "初始检测到 Nginx Master 进程存在,不锁定PID,后续热升级前重新获取"
[ ! -x "${NGINX_BIN}" ] && error_exit "Nginx 二进制文件异常"
# 检查运行用户
id -u ${RUN_USER} >/dev/null 2>&1 || {
log "运行用户 ${RUN_USER} 不存在,自动创建"
useradd -s /sbin/nologin ${RUN_USER}
}
# ==================== 2. 全量备份 ====================
log "===== 2. 执行目录备份 ====="
cp -a ${NGINX_HOME} ${BACKUP_DIR} || error_exit "目录备份失败"
${NGINX_BIN} -V > ${NGINX_HOME}/version_bak.txt 2>&1
log "备份完成:${BACKUP_DIR}"
# ==================== 3. 提取原有编译参数 ====================
log "===== 3. 读取原有编译参数 ====="
OLD_COMPILE_ARGS=$(${NGINX_BIN} -V 2>&1 | grep "configure arguments" | sed 's/^.*arguments: //')
[ -z "${OLD_COMPILE_ARGS}" ] && error_exit "获取编译参数失败"
log "原有编译参数:${OLD_COMPILE_ARGS}"
# ==================== 4. 安装依赖 + 准备源码 ====================
log "===== 4. 安装编译依赖 ====="
yum install -y pcre pcre-devel gcc-c++ zlib zlib-devel openssl openssl-devel wget unzip || error_exit "依赖安装失败"
cd ${PACKAGE_PATH}
# 下载源码
if [ ! -f "${NGINX_TAR}" ]; then
log "下载 Nginx 1.30.1 源码包"
wget -T 20 –tries=2 https://nginx.org/download/${NGINX_TAR} || error_exit "源码下载失败"
fi
# 解压源码
[ ! -d "${NGINX_SRC_DIR}" ] && tar -zxvf ${NGINX_TAR} || log "源码目录已存在,跳过解压"
# ==================== 5. 编译新版 Nginx ====================
log "===== 5. 编译 Nginx 1.30.1 ====="
cd ${NGINX_SRC_DIR}
./configure ${OLD_COMPILE_ARGS} || error_exit "configure 预编译失败"
make || error_exit "make 编译失败"
log "Nginx 编译完成"
# ==================== 6. 替换二进制文件 ====================
log "===== 6. 替换线上二进制程序 ====="
cd ${NGINX_HOME}/sbin
mv nginx nginx.old.$(date +%F_%H%M%S)
/bin/cp ${NGINX_SRC_DIR}/objs/nginx ./
chmod +x nginx
# 校验版本与配置
${NGINX_BIN} -v
${NGINX_BIN} -t || error_exit "新版配置语法错误"
log "二进制替换 + 配置校验通过"
# ==================== 7. 统一 HTTP2 配置 ====================
log "===== 7. 统一 HTTP2 配置语法 ====="
cd ${NGINX_CONF}
find . -name "*.conf" -type f -exec sed -i \\
's/listen 443 ssl http2;/listen 443 ssl;\\n http2 on;/g' {} \\;
log "HTTP2 配置处理完成"
# ==================== 8. 平滑热升级(执行前重新获取并校验Master PID) ====================
log "===== 8. 执行平滑热升级 ====="
# 重新实时获取当前 Master PID
CUR_UPGRADE_PID_LIST=($(get_nginx_master_pids))
VALID_UPGRADE_PIDS=()
for pid in "${CUR_UPGRADE_PID_LIST[@]}"; do
if is_pid_alive "${pid}"; then
VALID_UPGRADE_PIDS+=("${pid}")
fi
done
if [[ ${#VALID_UPGRADE_PIDS[@]} -ne 1 ]]; then
error_exit "热升级前未获取到唯一存活的 Nginx Master 进程,禁止继续执行!当前有效PID列表: ${VALID_UPGRADE_PIDS[*]}"
fi
MASTER_PID="${VALID_UPGRADE_PIDS[0]}"
log "热升级前获取到当前唯一 Master PID: ${MASTER_PID}"
kill -USR2 ${MASTER_PID}
sleep 3
kill -WINCH ${MASTER_PID}
echo -e "\\n============================================="
echo "请观测业务 5~15 分钟,确认访问正常!"
read -p "观测无误,下线旧进程?(y/n) " DOWN_CHOICE
if [[ ${DOWN_CHOICE} == [Yy] ]]; then
# 下线前再次校验该PID是否仍存活
if ! is_pid_alive "${MASTER_PID}"; then
error_exit "准备下线时,旧Master PID ${MASTER_PID} 已不存在,请人工核查!"
fi
kill -QUIT ${MASTER_PID}
log "旧 Master 进程已执行优雅下线指令"
sleep 2
else
log "暂不下线旧进程,升级暂停"
exit 0
fi
# ==================== 9. 最终校验(增加进程存活校验+无进程自动启动兜底) ====================
log "===== 9. 升级最终校验 ====="
MASTER_PID_LIST=($(get_nginx_master_pids))
VALID_PIDS=()
for pid in "${MASTER_PID_LIST[@]}"; do
if is_pid_alive "${pid}"; then
VALID_PIDS+=("${pid}")
fi
done
PID_NUM=${#VALID_PIDS[@]}
CUR_MASTER_PID="${VALID_PIDS[0]}"
if [[ ${PID_NUM} -eq 0 ]]; then
log "【严重异常】未检测到任何存活的 Nginx Master 进程!"
log "当前系统 nginx 相关进程快照:"
ps aux | grep -E 'nginx' | grep -v grep || true
log "前期已校验配置语法正常,尝试执行 systemctl start nginx 应急恢复服务"
if systemctl start nginx; then
log "systemctl start nginx 执行成功,重新检测 Master 进程"
sleep 2
MASTER_PID_LIST=($(get_nginx_master_pids))
VALID_PIDS=()
for pid in "${MASTER_PID_LIST[@]}"; do
if is_pid_alive "${pid}"; then
VALID_PIDS+=("${pid}")
fi
done
PID_NUM=${#VALID_PIDS[@]}
CUR_MASTER_PID="${VALID_PIDS[0]}"
if [[ ${PID_NUM} -eq 0 ]]; then
log "尝试启动 nginx 后仍无存活 Master 进程,需人工深度排查"
error_exit "Nginx 进程全部异常退出,自动恢复失败,请使用备份目录 ${BACKUP_DIR} 回滚!"
else
log "自动应急恢复成功,检测到 ${PID_NUM} 个存活 Master 进程"
fi
else
log "systemctl start nginx 执行失败,无法自动恢复"
error_exit "Nginx 进程全部异常退出,自动恢复失败,请使用备份目录 ${BACKUP_DIR} 回滚!"
fi
fi
log "当前运行 Nginx Master PID: ${CUR_MASTER_PID}"
RUN_BIN=$(readlink -f /proc/${CUR_MASTER_PID}/exe 2>/dev/null || echo "无法读取进程二进制路径")
log "进程实际调用二进制: ${RUN_BIN}"
ps aux | grep nginx | grep -v grep
if [[ -x "${RUN_BIN}" ]]; then
${RUN_BIN} -V
else
log "无法执行二进制版本查看(进程可能已退出或权限问题)"
fi
log "============================================="
log " Nginx 升级至 1.30.1 完成,漏洞已修复 "
log " 备份目录:${BACKUP_DIR}"
log " 操作日志:${LOG_FILE}"
log "============================================="

