Nginx生产环境编译配置升级回退新增模块全解析
一、Nginx 生产环境编译配置解读
./configure \# ===== 安装路径与运行用户 =====--prefix=/opt/nginx \ # 安装根目录(推荐自定义,避免与系统包冲突)--user=www \ # 运行 worker 进程的用户(需提前创建)--group=www \ # 运行 worker 进程的用户组(需提前创建)# ===== 核心功能模块(生产必须) =====--with-http_ssl_module \ # 启用 HTTPS 支持(生产环境必备)--with-http_v2_module \ # 启用 HTTP/2 协议(提升并发与加载速度)--with-http_realip_module \ # 获取真实客户端 IP(配合 X-Forwarded-For,反向代理场景必备)--with-http_stub_status_module \ # 启用状态监控模块(用于 Prometheus/Zabbix 监控,必备)# ===== 性能优化模块 =====--with-http_gzip_static_module \ # 支持发送预压缩的 .gz 静态文件(节省带宽、提升速度)--with-http_slice_module \ # 支持大文件分片传输(CDN/视频点播场景)--with-http_secure_link_module \ # 支持生成/校验安全链接(用于防盗链、临时授权下载)--with-http_auth_request_module \ # 支持基于子请求的认证(如对接 OAuth/JWT 鉴权服务)--with-pcre-jit \ # 启用 PCRE JIT 编译(大幅提升 location/rewrite 正则匹配性能)--with-file-aio \ # 启用 Linux AIO 异步文件 I/O(避免磁盘阻塞 Event Loop)--with-threads \ # 启用线程池支持(配合 aio threads; 或 proxy 多线程,避免阻塞)# ===== 流媒体与特殊格式支持(按需启用) =====--with-http_flv_module \ # 支持 FLV 伪流媒体(旧式视频网站,可选)--with-http_mp4_module \ # 支持 MP4 伪流媒体(现代视频网站推荐启用)--with-http_gunzip_module \ # 支持解压 gzip 内容(用于调试或缓存处理,非必需)# ===== TCP/UDP 代理支持(如需代理数据库、游戏服等) =====--with-stream \ # 启用 TCP/UDP 四层代理(非 Web 服务也需要时启用)--with-stream_ssl_module \ # 支持 stream 层 SSL(如 MySQL over SSL)--with-stream_realip_module \ # stream 层获取真实客户端 IP# ===== 文本处理模块(按需启用) =====--with-http_addition_module \ # 支持在响应头/尾追加内容(如插入统计代码)--with-http_sub_module \ # 支持响应内容替换(如替换 HTML 中的域名)# ===== 安全加固:禁用危险或无用模块 =====--without-http_autoindex_module \ # 禁用目录自动索引(防止信息泄露,安全必备!)--without-http_ssi_module \ # 禁用 SSI(服务器端包含,易被注入攻击)--without-http_userid_module \ # 禁用 userid 模块(生成客户端跟踪 Cookie,隐私合规风险)--without-http_empty_gif_module \ # 禁用空 GIF 模块(几乎无用,节省资源)# ===== 编译器安全与优化选项(强烈推荐) =====--with-cc-opt='-O2 -g -pipe -Wall \-Wp,-D_FORTIFY_SOURCE=2 \-fexceptions -fstack-protector-strong \--param=ssp-buffer-size=4 \-grecord-gcc-switches \-m64 -mtune=generic \-fPIC' \ # 启用编译优化 + 栈保护 + RELRO 预备 + 位置无关代码--with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' # 链接器安全选项:启用 RELRO + 立即绑定 + PIE(防 GOT 覆盖攻击)
二、编译安装脚本
cat install_nginx_v3.sh
#!/bin/bash# =============================================================================
# Nginx 编译安装脚本 (Debian 官方优化完整版)
# 支持 CentOS/RHEL/Rocky/Debian/Ubuntu
# 对齐 Debian 官方包编译参数,保留动态模块,日志使用 /var/log/nginx
# 自动配置 conf.d、安全头、logrotate、systemd、软链接等
# =============================================================================# 定义颜色输出
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color# 函数:显示信息
info() {echo -e "${GREEN}[INFO]${NC} $1"
}# 函数:显示警告
warn() {echo -e "${YELLOW}[WARN]${NC} $1"
}# 函数:显示错误并退出
error_exit() {echo -e "${RED}[ERROR]${NC} $1"exit 1
}# 默认配置
NGINX_VERSION="1.26.3"
INSTALL_PREFIX="/opt/nginx-${NGINX_VERSION}"
NGINX_USER="www"
NGINX_GROUP="www"
TEMP_DIR="/tmp/nginx-install-$(date +%s)"
SYSTEMD_SERVICE_FILE="/etc/systemd/system/nginx.service"# 清理函数(确保退出时清理临时文件)
cleanup() {if [ -d "$TEMP_DIR" ]; theninfo "清理临时目录: $TEMP_DIR"rm -rf "$TEMP_DIR" 2>/dev/null || warn "清理临时目录失败,请手动删除"fi
}
trap cleanup EXIT# 显示帮助
show_help() {echo "Usage: $0 [OPTIONS]"echo "Options:"echo " -v VERSION Nginx version (default: $NGINX_VERSION)"echo " -p PREFIX Install prefix (default: $INSTALL_PREFIX)"echo " -u USER Nginx user (default: $NGINX_USER)"echo " -g GROUP Nginx group (default: $NGINX_GROUP)"echo " -h Show this help message"exit 0
}# 解析参数
while getopts "v:p:u:g:h" opt; docase ${opt} inv) NGINX_VERSION=${OPTARG} ;;p) INSTALL_PREFIX=${OPTARG} ;;u) NGINX_USER=${OPTARG} ;;g) NGINX_GROUP=${OPTARG} ;;h) show_help ;;*) error_exit "Invalid option. Use -h for help." ;;esac
doneinfo "开始安装 Nginx $NGINX_VERSION 到 $INSTALL_PREFIX"# 检查是否已安装
if command -v nginx &> /dev/null; thenwarn "Nginx 已安装,路径:$(which nginx)"read -p "是否继续重新安装?(y/N): " -n 1 -recho[[ ! $REPLY =~ ^[Yy]$ ]] && { info "安装已取消"; exit 0; }
fi# 创建临时目录
info "创建临时目录: $TEMP_DIR"
mkdir -p "$TEMP_DIR" || error_exit "创建临时目录失败"
cd "$TEMP_DIR" || error_exit "切换目录失败"# 检查磁盘空间(至少500MB)
check_space() {local required=500local avail=$(df "$TEMP_DIR" --output=avail | tail -1)local avail_mb=$((avail / 1024))[ "$avail_mb" -lt "$required" ] && error_exit "临时目录分区空间不足 ($avail_mb MB < $required MB)"
}
check_space# 安装依赖
if [ -f /etc/redhat-release ] || [ -f /etc/rocky-release ]; theninfo "检测到 RHEL 系列系统,安装依赖..."# 启用 CRB 仓库(Rocky 10 / RHEL 10 必需)if grep -q "release 10" /etc/os-release 2>/dev/null; theninfo "检测到 RHEL/Rocky 10,启用 CRB 仓库..."sudo dnf config-manager --set-enabled crb || error_exit "启用 CRB 仓库失败"fi# 使用 dnf 替代 yum(兼容性更好)sudo dnf update -y || error_exit "更新包列表失败"# 安装开发工具组(更可靠)sudo dnf groupinstall -y "Development Tools" || error_exit "安装 Development Tools 失败"# 再安装其他特定依赖sudo dnf install -y \pcre2 pcre2-devel \openssl openssl-devel \systemd-devel \zlib-devel \libxml2-devel \libxslt-devel \gd-devel \libxml2 \libxslt \perl-ExtUtils-Embed \|| error_exit "安装额外依赖失败"
elif [ -f /etc/debian_version ]; theninfo "检测到 Debian 系列系统,安装依赖..."while fuser /var/lib/dpkg/lock >/dev/null 2>&1 || fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; dowarn "等待其他包管理器进程完成..."sleep 5donesudo apt-get update -y || error_exit "更新包列表失败"sudo apt-get install -y build-essential libpcre3 libpcre3-dev libssl-dev libsystemd-dev zlib1g-dev libxml2-dev libxslt1-dev libgd-dev libgeoip-dev libperl-dev || error_exit "安装依赖失败"
elseerror_exit "不支持的操作系统发行版"
fi# 创建 nginx 用户和组
info "检查并创建用户和组: $NGINX_USER:$NGINX_GROUP"
if ! getent group "$NGINX_GROUP" > /dev/null; thensudo groupadd -r "$NGINX_GROUP" || error_exit "创建组失败"
fi
if ! id -u "$NGINX_USER" > /dev/null 2>&1; thensudo useradd -s /sbin/nologin -r -g "$NGINX_GROUP" "$NGINX_USER" || error_exit "创建用户失败"
fi# 下载源码(带备用镜像)
info "下载 Nginx $NGINX_VERSION 源码..."
wget --no-check-certificate "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" || \
wget --no-check-certificate "https://mirrors.aliyun.com/nginx/nginx-${NGINX_VERSION}.tar.gz" || \
error_exit "下载 Nginx 源码失败"# 解压
info "解压源码..."
tar -zxvf "nginx-${NGINX_VERSION}.tar.gz" || error_exit "解压失败"
cd "nginx-${NGINX_VERSION}" || error_exit "切换目录失败"# 配置编译参数(完整 Debian 官方优化 + 你的路径定制)
info "配置 Nginx (Debian 官方优化完整版)..."
./configure \--prefix="$INSTALL_PREFIX" \--conf-path=/etc/nginx/nginx.conf \--http-log-path=/var/log/nginx/access.log \--error-log-path=/var/log/nginx/error.log \--lock-path=/var/lock/nginx.lock \--pid-path=/run/nginx.pid \--modules-path="$INSTALL_PREFIX/modules" \--http-client-body-temp-path=/var/lib/nginx/body \--http-fastcgi-temp-path=/var/lib/nginx/fastcgi \--http-proxy-temp-path=/var/lib/nginx/proxy \--http-scgi-temp-path=/var/lib/nginx/scgi \--http-uwsgi-temp-path=/var/lib/nginx/uwsgi \--user="$NGINX_USER" \--group="$NGINX_GROUP" \--with-cc-opt='-g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fcf-protection -fPIC -D_FORTIFY_SOURCE=3' \--with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -Wl,-z,relro -Wl,-z,now -fPIC' \--with-compat \--with-debug \--with-pcre-jit \--with-threads \--with-file-aio \--with-http_ssl_module \--with-http_v2_module \--with-http_realip_module \--with-http_addition_module \--with-http_sub_module \--with-http_flv_module \--with-http_mp4_module \--with-http_gunzip_module \--with-http_gzip_static_module \--with-http_auth_request_module \--with-http_secure_link_module \--with-http_slice_module \--with-http_stub_status_module \--with-stream \--with-stream_ssl_module \--with-stream_realip_module \--with-http_image_filter_module=dynamic \--with-http_perl_module=dynamic \--with-http_xslt_module=dynamic \--with-mail=dynamic \--with-stream=dynamic \--without-http_autoindex_module \--without-http_ssi_module \--without-http_userid_module \--without-http_empty_gif_module \|| error_exit "配置失败"# 编译安装
info "编译 Nginx..."
make -j$(nproc) || error_exit "编译失败"
info "安装 Nginx..."
sudo make install || error_exit "安装失败"# 验证安装
[ ! -f "$INSTALL_PREFIX/sbin/nginx" ] && error_exit "安装失败,未找到 nginx 二进制文件"
info "Nginx 安装成功!"# 设置目录权限
sudo chown -R "$NGINX_USER:$NGINX_GROUP" "$INSTALL_PREFIX" || error_exit "设置权限失败"# =============================
# 创建运行时目录结构(官方路径)
# =============================
info "创建 Nginx 运行时目录..."
sudo mkdir -p /var/log/nginx /var/lib/nginx/{body,fastcgi,proxy,scgi,uwsgi} /run /var/lock || error_exit "创建运行时目录失败"
sudo chown -R "$NGINX_USER:$NGINX_GROUP" /var/log/nginx /var/lib/nginx || error_exit "设置运行时目录权限失败"
sudo chmod 755 /var/lib/nginx/{body,fastcgi,proxy,scgi,uwsgi}
sudo touch /run/nginx.pid /var/lock/nginx.lock 2>/dev/null || true
sudo chown "$NGINX_USER:$NGINX_GROUP" /run/nginx.pid /var/lock/nginx.lock 2>/dev/null || true# =============================
# 生产环境配置优化(核心部分)
# =============================# 创建标准 /etc/nginx 结构
info "创建生产级配置目录结构 (/etc/nginx/{conf.d,snippets})..."
sudo mkdir -p /etc/nginx/{conf.d,snippets} || error_exit "创建配置目录失败"# 设置权限
sudo chown -R root:root /etc/nginx
sudo chown -R "$NGINX_USER:$NGINX_GROUP" /var/log/nginx
sudo chmod 755 /etc/nginx /etc/nginx/conf.d /etc/nginx/snippets# 备份原始 nginx.conf(如果存在)
if [ -f "etc/nginx/nginx.conf" ]; theninfo "备份原始配置文件: /etc/nginx/nginx.conf → /etc/nginx/nginx.conf.bak"mv " /etc/nginx/nginx.conf" " /etc/nginx/nginx.conf.bak" || warn "备份原始配置失败"
fi# 生成主配置文件 nginx.conf
info "生成生产级 nginx.conf..."
cat > "/etc/nginx/nginx.conf" <<EOF
user $NGINX_USER;
worker_processes auto;error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;events {worker_connections 1024;use epoll;multi_accept on;
}http {include mime.types;default_type application/octet-stream;charset utf-8;sendfile on;tcp_nopush on;tcp_nodelay on;keepalive_timeout 65;types_hash_max_size 2048;# 安全加固server_tokens off;client_max_body_size 50M;# 日志格式log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ''\$status \$body_bytes_sent "\$http_referer" ''"\$http_user_agent" "\$http_x_forwarded_for"';access_log /var/log/nginx/access.log main;# gzip 压缩gzip on;gzip_vary on;gzip_min_length 1k;gzip_comp_level 6;gzip_types text/plain text/css application/json application/javascript text/xml application/xml;# 引入安全头配置include /etc/nginx/snippets/security.conf;# 核心:加载所有站点配置include /etc/nginx/conf.d/*.conf;# 默认兜底 server(拒绝未定义 Host 的请求)server {listen 80 default_server;server_name _;return 444;}
}
EOF# 设置权限
sudo chown root:root /etc/nginx/nginx.conf
sudo chmod 644 /etc/nginx/nginx.conf
sudo chown root:root /etc/nginx/mime.types 2>/dev/null || true
sudo chmod 644 /etc/nginx/mime.types 2>/dev/null || true# 可选:复制其他常用参数文件
info "复制 fastcgi/scgi/uwsgi 参数文件..."
sudo cp "$INSTALL_PREFIX/conf/fastcgi_params" /etc/nginx/ 2>/dev/null || true
sudo cp "$INSTALL_PREFIX/conf/scgi_params" /etc/nginx/ 2>/dev/null || true
sudo cp "$INSTALL_PREFIX/conf/uwsgi_params" /etc/nginx/ 2>/dev/null || true
sudo chown root:root /etc/nginx/*_params 2>/dev/null || true
sudo chmod 644 /etc/nginx/*_params 2>/dev/null || true# 创建安全头片段
info "创建安全响应头配置..."
cat > "/etc/nginx/snippets/security.conf" <<'EOF'
# 安全响应头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# add_header Content-Security-Policy "default-src 'self';" always;
EOF# 创建默认站点
info "创建默认站点配置..."
cat > "/etc/nginx/conf.d/default.conf" <<EOF
server {listen 80;server_name localhost;root $INSTALL_PREFIX/html;index index.html index.htm;location / {try_files \$uri \$uri/ =404;}access_log /var/log/nginx/default.access.log main;error_log /var/log/nginx/default.error.log warn;
}
EOF# 创建 systemd 服务文件
info "创建 systemd 服务文件..."
cat <<EOF | sudo tee "$SYSTEMD_SERVICE_FILE" > /dev/null
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=$INSTALL_PREFIX/sbin/nginx -c /etc/nginx/nginx.conf -t
ExecStart=$INSTALL_PREFIX/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=$INSTALL_PREFIX/sbin/nginx -c /etc/nginx/nginx.conf -s reload
ExecStop=/bin/kill -s QUIT \$MAINPID
PrivateTmp=true
LimitNOFILE=65535[Install]
WantedBy=multi-user.target
EOF# 重新加载 systemd
sudo systemctl daemon-reload || error_exit "systemd 重载失败"# 创建软链接(标准化路径)
info "创建标准化软链接..."
sudo ln -sf "$INSTALL_PREFIX" /opt/nginx
sudo ln -sf "$INSTALL_PREFIX/sbin/nginx" /usr/local/bin/nginx# 配置 logrotate
info "配置日志轮转..."
cat <<EOF | sudo tee /etc/logrotate.d/nginx > /dev/null
/var/log/nginx/*.log {dailymissingokrotate 52compressdelaycompressnotifemptycreate 640 $NGINX_USER $NGINX_GROUPsharedscriptspostrotateif systemctl is-active --quiet nginx; thensystemctl reload nginxfiendscript
}
EOF# 启动服务
info "启动 Nginx 服务..."
sudo systemctl start nginx || error_exit "启动失败"
sudo systemctl enable nginx || error_exit "设置开机自启失败"# 测试配置
info "测试 Nginx 配置..."
nginx -t || error_exit "配置测试失败"# 健康检查
info "健康检查:访问 localhost..."
sleep 2
if curl -s --connect-timeout 5 http://localhost >/dev/null; theninfo " Nginx 正常运行并响应请求"
elsewarn " Nginx 未返回预期响应,请检查防火墙或配置"
fi# 输出信息
echo
info " Nginx 安装和配置成功完成!"
echo "----------------------------------------------------------------------------"
echo "版本: $NGINX_VERSION"
echo "安装路径: $INSTALL_PREFIX"
echo "模块路径: $INSTALL_PREFIX/modules"
echo "配置路径: /etc/nginx/"
echo " ├── nginx.conf"
echo " ├── conf.d/ # 站点配置"
echo " └── snippets/ # 复用片段"
echo "日志路径: /var/log/nginx/"
echo "临时路径: /var/lib/nginx/{body,proxy,fastcgi,scgi,uwsgi}"
echo "PID 路径: /run/nginx.pid"
echo
echo "管理命令:"
echo " systemctl start|stop|restart|reload|status nginx"
echo " nginx -t # 测试配置"
echo " nginx -s reload # 重载配置"
echo
echo "示例:添加新站点"
echo " sudo vim /etc/nginx/conf.d/myapp.conf"
echo " sudo systemctl reload nginx"
echo "----------------------------------------------------------------------------"
echoinfo " 安装完成!生产环境就绪。"
三、平滑升级
1.观察一天后,若没有占用worker直接执行 kill -QUIT 旧master
#!/bin/bash
#----------------------------------------------------
#1.整体变量赋值
#----------------------------------------------------NGINX_VERSION="1.28.0"
WORK_DIR="/tmp"
INSTALL_PREFIX="/opt/nginx-${NGINX_VERSION}"
SOURCE_TAR="nginx-${NGINX_VERSION}.tar.gz"
SOURCE_URL="https://nginx.org/download/${SOURCE_TAR}"
#SOURCE_DIR="${WORK_DIR}/nginx-${NGINX_VERSION}"#-----------------------------------------------------
#2准备新版本,备份老版本二进制方便回滚
#-----------------------------------------------------
#2.1获取老版本
oldversion=$(nginx -v 2>&1 | cut -d'/' -f2)
#2.1.1.输出 旧版本版本号 和 新版本版本号方便回退
echo "输出旧版本版本号和新版本版本号方便回退"
echo "$oldversion" >> /tmp/oldversion
echo "$NGINX_VERSION" >> /tmp/newversion
#2.2获取编译配置
NGINX_V_OUTPUT=$(nginx -V 2>&1 | sed -n 's/.*configure arguments: //p')
#2.3进入源码目录
cd $WORK_DIR
#1.4下载源码包
wget $SOURCE_URL
#2.5解压到/opt
tar -zxvf $SOURCE_TAR -C /opt
#2.6备份旧二进制文件
NGINX_BIN=$(readlink -f "$(which nginx)")
# 替换
mv "$NGINX_BIN" "${NGINX_BIN}.${oldversion}.old"#2.7进入解压后的新目录编译
cd /opt/nginx-${NGINX_VERSION}# 清理路径参数,强制使用新 prefix
clean_args=$(echo "$NGINX_V_OUTPUT" | \sed -E \-e "s|--prefix=[^ ]*|--prefix=${INSTALL_PREFIX}|g" \-e "s|--modules-path=[^ ]*|--modules-path=${INSTALL_PREFIX}/modules|g" \
)eval "./configure $clean_args"sleep 1
echo "等待编译"
make || { echo "编译失败!"; exit 1; }
#2.8复制新版本二进制文件到老版本二进制文件
cp $INSTALL_PREFIX/objs/nginx ${NGINX_BIN}#-------------------------------------------------------
#3.平滑升级
#-------------------------------------------------------#3.1发送信号启动新版本主进程
#3.1.1查找nginx 旧master id
PID_FILE=$(nginx -T 2>/dev/null | grep -m1 '^[[:space:]]*pid[[:space:]]' | awk '{print $2}' | sed 's/;//')
OLD_MASTER_PID=$(cat "$PID_FILE")
#3.1.1.1输出旧主进程pid 以及新主进程pid 到对应文件保存
echo "输出旧主进程pid以及新主进程pid。到/tmp/OLD_MASTER_PID以及/tmp/NEW_MASTER_PID"
echo $OLD_MASTER_PID >> /tmp/OLD_MASTER_PID
#3.1.2发送 USR2 信号,启动新版本主进程
kill -USR2 $OLD_MASTER_PID
sleep 2
#再次查看进程,会发现两个主进程(旧 进程 + 新 PID,如 1221)
#3.13 逐步关闭旧版本工作进程
kill -WINCH $OLD_MASTER_PID
sleep 2
#3.14监控旧worker是否有调用
#查找nginx 新master id
NEW_MASTER_PID=$(cat "$PID_FILE")
echo $NEW_MASTER_PID >> /tmp/NEW_MASTER_PID# -------------------------------------------------------
# 监控旧 master 的 worker 是否已全部退出
# -------------------------------------------------------
check_old_workers() {if ! kill -0 "$OLD_MASTER_PID" 2>/dev/null; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧主进程已退出"return 1fi# 检查是否有旧 worker:父进程是 OLD_MASTER_PID 且命令含 'nginx: worker'OLD_WORKERS=$(pgrep -P "$OLD_MASTER_PID" | while read pid; doif [ -n "$pid" ] && ps -p "$pid" -o cmd= 2>/dev/null | grep -q 'nginx: worker'; thenecho "$pid"fidone)if [ -z "$OLD_WORKERS" ]; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 worker 已全部退出,正在退出旧 master..."kill -QUIT "$OLD_MASTER_PID" 2>/dev/null || trueecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 master 已结束"return 0elseecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 master 仍有 worker: $OLD_WORKERS"return 2fi
}# 每 30 秒检查一次,最多等待 1 天(2880 次)
echo "$(date '+%Y-%m-%d %H:%M:%S') - 开始监控旧 worker,最多等待 1天..."
for i in $(seq 1 2880); docheck_old_workersret=$?if [ $ret -eq 0 ] || [ $ret -eq 1 ]; thenbreakfisleep 30
done# 兜底:1 天后无论是否有 worker,都强制退出旧 master
if kill -0 "$OLD_MASTER_PID" 2>/dev/null; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 监控超时(60秒),尝试强制结束旧 master (PID: $OLD_MASTER_PID)..."# 发送 QUIT 信号kill -QUIT "$OLD_MASTER_PID" 2>/dev/null || true# 等待 1 秒让信号处理sleep 1# 检查是否还存活if kill -0 "$OLD_MASTER_PID" 2>/dev/null; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 警告:旧 master (PID: $OLD_MASTER_PID) 仍在运行,QUIT 未使其退出"elseecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 master 已成功退出"fi
elseecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 master 已不在,无需处理"
fiecho "$(date '+%Y-%m-%d %H:%M:%S') - 强制结束 master 流程完成"
# 输出当前 nginx 进程状态
echo "$(date '+%Y-%m-%d %H:%M:%S') - 当前 nginx 进程状态:"
ps auxf | grep nginx | grep -v grep
#6.验证版本号
echo $(nginx -v)
echo "$(curl -I localhost)"
2.支持平滑回退的版本
#!/bin/bash
#----------------------------------------------------
#1.整体变量赋值
#----------------------------------------------------NGINX_VERSION="1.28.0"
WORK_DIR="/tmp"
INSTALL_PREFIX="/opt/nginx-${NGINX_VERSION}"
SOURCE_TAR="nginx-${NGINX_VERSION}.tar.gz"
SOURCE_URL="https://nginx.org/download/${SOURCE_TAR}"
#SOURCE_DIR="${WORK_DIR}/nginx-${NGINX_VERSION}"#-----------------------------------------------------
#2准备新版本,备份老版本二进制方便回滚
#-----------------------------------------------------
#2.1获取老版本
oldversion=$(nginx -v 2>&1 | cut -d'/' -f2)
#2.1.1.输出 旧版本版本号 和 新版本版本号方便回退
echo "输出旧版本版本号和新版本版本号方便回退"
echo "$oldversion" >> /tmp/oldversion
echo "$NGINX_VERSION" >> /tmp/newversion
#2.2获取编译配置
NGINX_V_OUTPUT=$(nginx -V 2>&1 | sed -n 's/.*configure arguments: //p')
#2.3进入源码目录
cd $WORK_DIR
#1.4下载源码包
wget $SOURCE_URL
#2.5解压到/opt
tar -zxvf $SOURCE_TAR -C /opt
#2.6备份旧二进制文件
NGINX_BIN=$(readlink -f "$(which nginx)")
# 替换
mv "$NGINX_BIN" "${NGINX_BIN}.${oldversion}.old"#2.7进入解压后的新目录编译
cd /opt/nginx-${NGINX_VERSION}# 清理路径参数,强制使用新 prefix
clean_args=$(echo "$NGINX_V_OUTPUT" | \sed -E \-e "s|--prefix=[^ ]*|--prefix=${INSTALL_PREFIX}|g" \-e "s|--modules-path=[^ ]*|--modules-path=${INSTALL_PREFIX}/modules|g" \
)eval "./configure $clean_args"sleep 1
echo "等待编译"
make || { echo "编译失败!"; exit 1; }
#2.8复制新版本二进制文件到老版本二进制文件
cp $INSTALL_PREFIX/objs/nginx ${NGINX_BIN}#-------------------------------------------------------
#3.平滑升级
#-------------------------------------------------------#3.1发送信号启动新版本主进程
#3.1.1查找nginx 旧master id
PID_FILE=$(nginx -T 2>/dev/null | grep -m1 '^[[:space:]]*pid[[:space:]]' | awk '{print $2}' | sed 's/;//')
OLD_MASTER_PID=$(cat "$PID_FILE")
#3.1.1.1输出旧主进程pid 以及新主进程pid 到对应文件保存
echo "输出旧主进程pid以及新主进程pid。到/tmp/OLD_MASTER_PID以及/tmp/NEW_MASTER_PID"
echo $OLD_MASTER_PID >> /tmp/OLD_MASTER_PID
#3.1.2发送 USR2 信号,启动新版本主进程
kill -USR2 $OLD_MASTER_PID
sleep 2
#再次查看进程,会发现两个主进程(旧 进程 + 新 PID,如 1221)
#3.13 逐步关闭旧版本工作进程
kill -WINCH $OLD_MASTER_PID
sleep 2
#3.14监控旧worker是否有调用
#查找nginx 新master id
NEW_MASTER_PID=$(cat "$PID_FILE")
echo $NEW_MASTER_PID >> /tmp/NEW_MASTER_PID# ==============================================================================
# 替换开始:以下为新的监控逻辑(仅监控,不自动 kill -QUIT)
# ==============================================================================monitor_old_workers() {if ! kill -0 "$OLD_MASTER_PID" 2>/dev/null; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧主进程 (PID: $OLD_MASTER_PID) 已退出"return 1fi# 获取旧 master 的所有子进程,并筛选出 nginx workerOLD_WORKERS=""while IFS= read -r pid; doif [ -n "$pid" ] && ps -p "$pid" -o cmd= 2>/dev/null | grep -q 'nginx: worker'; thenOLD_WORKERS="$OLD_WORKERS $pid"fidone < <(pgrep -P "$OLD_MASTER_PID")if [ -z "$OLD_WORKERS" ]; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 worker 已全部退出!"return 0elseecho "$(date '+%Y-%m-%d %H:%M:%S') - 旧 master 仍有 worker:$(echo $OLD_WORKERS)"return 2fi
}echo
echo "$(date '+%Y-%m-%d %H:%M:%S') - [监控模式] 旧 master (PID: $OLD_MASTER_PID) 正在优雅退出中..."
echo " 脚本将仅监控旧 worker 状态,不会自动发送 QUIT 信号。"
echo " 一旦旧 worker 全部退出,将立即提示。"
echo " 每 10 分钟通报一次状态。"
echo " 按回车后输入 'y' 可随时退出监控(退出后可手动决定是否 kill -QUIT)。"
echo# 启动用户输入监听(非阻塞)
exec 3<&0 # 保存 stdin
{read -r user_input
} <&3 &# 主监控循环
ten_min_counter=0
while true; domonitor_old_workersret=$?# 如果旧 worker 已全部退出,立即提示(但不停止)if [ $ret -eq 0 ] || [ $ret -eq 1 ]; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - 提示:旧 master 可安全退出(如需,可手动执行:kill -QUIT $OLD_MASTER_PID)"fi# 每 10 分钟(60 次 * 10 秒)输出一次摘要if [ $((++ten_min_counter % 60)) -eq 0 ]; thenecho "$(date '+%Y-%m-%d %H:%M:%S') - [10分钟通报] 仍在监控旧 master (PID: $OLD_MASTER_PID)..."fi# 每 10 秒检查一次用户输入,避免长时间无响应sleep 10# 检查用户是否输入了 'y'if [ "$user_input" = "y" ]; thenechoecho "$(date '+%Y-%m-%d %H:%M:%S') - 用户退出监控。"echo " 当前状态:"ps auxf | grep nginx | grep -v grepechoecho "如需完全退出旧 master,请手动执行:"echo " kill -QUIT $OLD_MASTER_PID"breakfi
done# ==============================================================================
# 替换结束
# ==============================================================================# 最终验证
echo
echo "$(date '+%Y-%m-%d %H:%M:%S') - 当前 nginx 进程状态:"
ps auxf | grep nginx | grep -v grepecho
echo "$(date '+%Y-%m-%d %H:%M:%S') - 验证当前 nginx 版本:"
nginx -v 2>&1echo
echo "$(date '+%Y-%m-%d %H:%M:%S') - 测试 HTTP 响应:"
curl -I localhost 2>/dev/null || echo "无法连接 localhost"
四、平滑回退
1.针对已经kill QUIT master就只能重启方式来执行了
# 1. 停止新版本 Nginx
systemctl stop nginx# 2. 恢复旧版本二进制文件
mv /opt/nginx-1.26.3/sbin/nginx.1.26.3.old /opt/nginx-1.26.3/sbin/nginx# 3. 启动旧版本 Nginx
systemctl start nginx# 4. 验证回滚结果
nginx -v # 应显示旧版本
2.针对没有kill QUIT master 可以平滑回退
#!/bin/bash
set -eOLD_MASTER_PID=$(cat /tmp/OLD_MASTER_PID)
NEW_MASTER_PID=$(cat /tmp/NEW_MASTER_PID)
OLD_VERSION=$(cat /tmp/oldversion)
NGINX_BIN=$(readlink -f "$(which nginx)")echo "开始回退到旧版本: $OLD_VERSION"# 检查旧 master 是否还在
if ! kill -0 "$OLD_MASTER_PID" 2>/dev/null; thenecho " 错误:旧 master (PID $OLD_MASTER_PID) 已退出,无法平滑回退!"echo " 请使用完全重启方式回退。"exit 1
fi# 检查新 master 是否还在
if ! kill -0 "$NEW_MASTER_PID" 2>/dev/null; thenecho " 新 master 已退出,仅恢复二进制..."
fi# 1. 恢复旧二进制
echo "恢复旧二进制文件..."
mv "${NGINX_BIN}.${OLD_VERSION}.old" "$NGINX_BIN"# 2. 通知旧 master 重新加载(启动旧 worker)
echo "发送 HUP 给旧 master ($OLD_MASTER_PID)..."
kill -HUP "$OLD_MASTER_PID"
sleep 2# 3. 如果新 master 还在,关闭它
if kill -0 "$NEW_MASTER_PID" 2>/dev/null; thenecho "发送 WINCH 给新 master ($NEW_MASTER_PID)..."kill -WINCH "$NEW_MASTER_PID"sleep 3# 退出新 masterif kill -0 "$NEW_MASTER_PID" 2>/dev/null; thenkill -QUIT "$NEW_MASTER_PID"fi
fi# 4. 验证
echo "当前版本: $(nginx -v 2>&1)"
ps aux | grep '[n]ginx'
echo " 回退完成"
五、平滑加载模块
平滑加载模块本质上和平滑升级类似:
手动以及原理参考 :https://blog.csdn.net/lizhengyu891231/article/details/136499381?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522cefd6b71ffdb3f80e41266dc261f060e%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=cefd6b71ffdb3f80e41266dc261f060e&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-136499381-null-null.142^v102^pc_search_result_base8&utm_term=nginx%20%E6%B7%BB%E5%8A%A0%E7%AC%AC%E4%B8%89%E6%96%B9%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187
脚本
#!/bin/bash
#----------------------------------------------------
# 脚本目标:在**不改变 Nginx 版本**的前提下,重新编译并添加第三方模块
#----------------------------------------------------# ====== 配置区 ======
# 第三方模块路径(可添加多个,用空格分隔)
THIRD_PARTY_MODULES=("/path/to/your/module1""/path/to/your/module2"
)
# ===================WORK_DIR="/tmp"# 1. 获取当前运行的 Nginx 版本
oldversion=$(nginx -v 2>&1 | cut -d'/' -f2)
if [[ -z "$oldversion" ]]; thenecho "错误:无法获取当前 Nginx 版本,请确认 nginx 命令可用。"exit 1
fiecho "当前 Nginx 版本: $oldversion"# 2. 记录版本用于回滚
echo "$oldversion" > /tmp/nginx_current_version
echo "$oldversion" > /tmp/nginx_target_version # 目标版本 = 当前版本# 3. 获取当前编译参数(-V 输出)
NGINX_V_OUTPUT=$(nginx -V 2>&1 | sed -n 's/.*configure arguments: //p')
if [[ -z "$NGINX_V_OUTPUT" ]]; thenecho "错误:无法获取 Nginx 编译参数。"exit 1
fi# 4. 构建第三方模块参数
ADD_MODULE_ARGS=""
for mod in "${THIRD_PARTY_MODULES[@]}"; doif [[ ! -d "$mod" ]]; thenecho "警告:模块路径不存在: $mod"continuefiADD_MODULE_ARGS+=" --add-module=$mod"
done# 5. 设置源码相关变量(使用当前版本)
SOURCE_TAR="nginx-${oldversion}.tar.gz"
SOURCE_URL="https://nginx.org/download/${SOURCE_TAR}"
SOURCE_DIR="${WORK_DIR}/nginx-${oldversion}"
INSTALL_PREFIX=$(echo "$NGINX_V_OUTPUT" | grep -oE -- '--prefix=[^ ]*' | cut -d= -f2)# 如果无法从参数中提取 prefix,则使用默认
if [[ -z "$INSTALL_PREFIX" ]]; thenINSTALL_PREFIX="/usr/local/nginx"echo "警告:未检测到 --prefix,使用默认路径: $INSTALL_PREFIX"
fi# 6. 进入工作目录
cd "$WORK_DIR" || { echo "无法进入 $WORK_DIR"; exit 1; }# 7. 下载源码(如果尚未存在)
if [[ ! -f "$SOURCE_TAR" ]]; thenecho "正在下载 Nginx 源码: $SOURCE_URL"wget "$SOURCE_URL" || { echo "下载失败"; exit 1; }
elseecho "源码包已存在,跳过下载"
fi# 8. 解压源码
if [[ ! -d "$SOURCE_DIR" ]]; thentar -zxvf "$SOURCE_TAR" -C "$WORK_DIR" || { echo "解压失败"; exit 1; }
elseecho "源码目录已存在,跳过解压"
fi# 9. 备份当前二进制文件
NGINX_BIN=$(readlink -f "$(which nginx)")
if [[ ! -f "$NGINX_BIN" ]]; thenecho "错误:找不到 Nginx 二进制文件"exit 1
fiBACKUP_BIN="${NGINX_BIN}.${oldversion}.old"
cp "$NGINX_BIN" "$BACKUP_BIN" || { echo "备份失败"; exit 1; }
echo "已备份原二进制文件到: $BACKUP_BIN"# 10. 进入源码目录
cd "$SOURCE_DIR" || { echo "无法进入源码目录"; exit 1; }# 11. 构建 configure 参数:保留原参数 + 添加新模块
# 注意:不修改 --prefix、--modules-path 等路径,保持与原环境一致
CONFIGURE_CMD="./configure $NGINX_V_OUTPUT $ADD_MODULE_ARGS"echo "执行配置命令:"
echo "$CONFIGURE_CMD"
eval "$CONFIGURE_CMD" || { echo "configure 失败"; exit 1; }# 12. 编译(不 install)
echo "开始编译..."
make -j$(nproc) || { echo "编译失败!"; exit 1; }# 13. 替换二进制文件(平滑替换)
echo "正在替换 Nginx 二进制文件..."
cp objs/nginx "$NGINX_BIN" || { echo "替换失败"; exit 1; }# 14. 验证新二进制
"$NGINX_BIN" -t && echo "配置测试通过!"
"$NGINX_BIN" -V 2>&1 | grep -q "your/module" && echo "第三方模块已加载!"echo " 重新编译完成!如需回滚,请执行:"
echo " cp '$BACKUP_BIN' '$NGINX_BIN'"
echo " systemctl reload nginx # 或 nginx -s reload"
使用说明:
- 修改
THIRD_PARTY_MODULES
数组,填入你的模块绝对路径。 - 确保这些模块目录包含
config
文件(标准 Nginx 模块结构)。 - 脚本会自动:
- 下载当前版本源码
- 保留原有所有编译参数
- 添加你的模块
- 编译后仅替换
nginx
二进制(不 touch 配置、日志、HTML 等)
- 回滚只需恢复备份的二进制 + reload