资讯动态

Nginx平滑升级实战:从编译参数到信号热切换的完整指南

发布时间:2026/10/13 22:38:37 来源:尧图企业网站定制
1. 动手升级前先摸清三件事当前状态、版本差异和依赖环境1.1 用一条命令摸清家底nginx -V 的正确解读先说一个很多教程不强调、但实战中特别容易出事的点升级nginx之前第一步不是去官网下源码而是先把线上正在运行的版本和编译参数完整捞出来。在服务器上执行nginx -V注意这里是大写的 V。它会输出类似这样的信息nginx version: nginx/1.24.0 built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) built with OpenSSL 1.0.2k-fips 26 Jan 2017 TLS SNI support enabled configure arguments: --prefix/usr/local/nginx --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-stream --with-stream_ssl_module --with-http_stub_status_module这一段configure arguments就是你后续编译新版本时必须保留的核心参数。我见过不止一次这样的场景同事升级nginx时图省事直接./configure --prefix/usr/local/nginx一把梭结果升级完以后一堆业务异常——WebSocket连不上、真实验IP变成了nginx内网IP、stream代理直接没了。最后查下来就是编译参数丢了几个module导致的。所以拿到nginx -V的输出后建议复制到记事本里后面编译新版本时逐项对照。另外顺手看一下nginx的版本号nginx -v小写v和nginx -V | head -1效果一样都可以确认当前版本。如果线上是1.18.0这种比较旧的版本升级时跳的步子就更大后面章节我会专门说版本跳级的事。1.2 目标版本怎么选稳定版优先不要盲目追新选目标版本这件事很多人随手下载一个最新mainline版本就上了这个习惯我劝你改一改。nginx的官方版本线分成两类类型版本特征适用场景Mainline版版本号中间是奇数如1.27.x新功能尝鲜、测试环境、短期项目稳定版版本号中间是偶数如1.24.x、1.26.x生产环境、长期运维、依赖较多场景我这次升级选的是稳定版的1.26.x系列。选择一个版本之后还要做一件很重要的事打开官方CHANGES文件从头到尾扫一遍从你当前版本到目标版本之间的变更记录。这个文件里会列出每一个小版本的功能变更、bug修复和配置行为变化。举几个我实际遇到的例子某些版本调整了日志格式默认字段有的版本对HTTP/2的某些指令做了废弃标记还有的版本修改了ssl_protocols的默认值。这些变更如果不提前知道升级完可能会发现日志解析脚本挂了、或者TLS配置行为和以前不一样。另外如果你用的是操作系统自带的nginx包比如yum或apt装的升级路径不太一样优先考虑通过包管理器升级到同系列最新版而不是混用源码编译。混用容易造成二进制位置、配置文件路径、pid文件路径不对齐的问题排查起来很痛苦。本文后面说的升级方式主要针对源码编译安装的nginx。1.3 依赖库与第三方模块的连带风险nginx编译依赖几个基础库PCRE正则支持、zlibgzip压缩、OpenSSLHTTPS和TLS。升级时如果环境里这些库的版本太老新版本nginx可能编译失败或者在运行时出现诡异行为。比如要使用HTTP/3或某些新TLS特性时对OpenSSL版本有明确要求编译前最好确认一下gcc --version pcre-config --version openssl version zlibconf --version # 或者 zlib.h 宏如果你依赖的是--with-http_ssl_module这种静态方式那OpenSSL跟随nginx一起编译问题不大但如果你的环境中有些库是通过动态链接方式引用的升级前就要评估库版本兼容性。还有一类风险最容易忽略第三方模块。比如你用了Lua扩展、防火墙类模块、某些控制面板自带的nginx插件这些模块往往是针对特定nginx版本编译的。升级nginx主版本后第三方模块如果没有同步更新轻则编译失败重则运行期段错误。检查方法也简单nginx -V的输出里如果带--add-module或者--add-dynamic-module参数就说明挂了第三方模块升级前需要逐一确认它们对目标版本的兼容性。提示建议在最开始就给线上nginx做好编译参数和模块清单的文档备份。没有文档的话每次升级都要靠回忆和猜风险完全不可控。2. 直接升级编译新版本并替换二进制全程可控可回滚2.1 下载源码与编译参数还原直接升级也叫停机升级或者二进制替换升级思路很简单新版本编译好后把旧的nginx二进制文件换掉重启进程。这种方式有一个短暂的中断窗口业务会断开几秒到几十秒但胜在流程简单、可控性好适合允许短暂停服的业务场景。先说下载源码。到nginx官网下载页找到目标版本的源码包比如nginx-1.26.x.tar.gz用wget拉下来后解压wget https://nginx.org/download/nginx-1.26.x.tar.gz tar zxvf nginx-1.26.x.tar.gz cd nginx-1.26.x然后开始configure这一步必须把之前nginx -V里看到的参数完完整整带回来。我这次线上环境的完整命令是这样的./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-stream \ --with-stream_ssl_module \ --with-http_gzip_static_module \ --with-pcre \ --with-cc-opt-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE2 -fexceptions -fstack-protector-strong --paramssp-buffer-size4 -grecord-switches -m64 -mtunegeneric \ --with-ld-opt-Wl,-z,relro -Wl,-z,now -pie这里的--with-cc-opt和--with-ld-opt一般是从系统自带的nginx配置里抄过来的安全编译选项如果你原来的nginx没有这些参数不加也行不影响功能只是编译出的二进制加固程度有差别。configure跑完之后检查一下输出的summary重点看有没有你期望的模块。如果缺少某个模块大概率是依赖库没装全回头补齐依赖再重新configure。2.2 只编译不安装手动替换关键文件Configure完成后执行make但注意不要直接make install。原因是make install会把新版文件铺满整个--prefix目录包括覆盖你的配置文件、日志目录结构、甚至清掉一些你手动添加的辅助脚本。而这些目录里往往存着线上真正的数据。所以推荐的做法是make编译完成后新版nginx二进制位于objs/nginx。接下来按顺序做这几件事# 1. 备份当前二进制 cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old # 2. 复制新二进制 cp objs/nginx /usr/local/nginx/sbin/nginx # 3. 检查新二进制是否能正确加载配置 /usr/local/nginx/sbin/nginx -tnginx -t这一步非常关键。如果配置文件中使用了新版已经废弃的指令或者语法上有变化-t会把错误直接报出来。我当时就遇到过一件事旧配置里写了某个老版本才认识的指令新版本已经移除了-t立刻给出unknown directive的提示才避免了一次上线事故。2.3 停旧启新验证端口和进程状态配置检查通过后执行摇身一变。先停旧进程再启新进程# 优雅停止旧进程 /usr/local/nginx/sbin/nginx -s quit # 启动新进程 /usr/local/nginx/sbin/nginx注意这里的顺序必须严格先停旧、再启新。如果不停旧直接启动新进程新的master进程会绑定失败报address already in use因为旧进程还占着端口。另外-s quit是优雅退出会等worker处理完当前请求后再退出如果用-s stop强制停正在进行的请求会被直接掐断。能接受几秒等待的话尽量用quit。启动后立刻检查几个东西ps -ef | grep nginx ss -lntp | grep 80确认master和worker进程都正常起来、端口正常监听后再做一轮简单的功能验证curl首页、本地带Host请求、检查error.log有没有新增报错。直接升级的优点是真出问题方便回滚把nginx.old改回nginx重启进程就还原了。缺点是那个中断窗口对某些业务来说可能无法接受。比如跑着大量WebSocket长连接的在线业务哪怕断开两秒都会造成大量用户掉线这种场景就必须用接下来要说的在线升级。3. 在线升级利用信号机制实现平滑热切换3.1 热升级原理master进程如何交接监听端口在线升级业内也叫热升级或平滑升级核心目标就是一句话新老进程交替的过程中监听端口不断开已有连接不断开新连接无缝交给新进程。要做到这个效果靠的是nginx master-worker架构和一组进程信号。这里先把原理讲透nginx启动后master进程一号进程负责管理整个生命周期worker进程实际处理连接。master进程在启动时创建监听socket然后fork出来的worker进程会继承这个socket。正常运行时大家共享同一个监听socket新连接进来后由内核负载均衡分给某个空闲worker。在线升级的过程是这样的新二进制已经就位但旧进程还在跑。给旧master发送USR2信号。旧master收到后会fork出一个新的master进程新master新master读取新配置、加载新模块然后fork出自己的worker进程。关键的机制来了新master启动时复用旧master已经创建好的监听socket文件描述符。也就是说新worker和旧worker实际上监听在同一个socket上新连接不会因为端口被占用而失败。新worker开始接收新连接旧worker继续处理它手上已有的连接。给旧master发送WINCH信号旧master会优雅关闭自己的worker也就是等它们处理完手头的请求后退出。最后给旧master发送QUIT信号旧master退出升级完成。这个过程中nginx把新老两个master进程之间通过特殊的进程通信机制传递了监听socket的继承关系所以端口始终没有被释放过连接也没有被掐断。3.2 标准操作流程与命令解读理论说完上实操。准备工作还是和直接升级一样先编译好新版本然后把objs/nginx复制到/usr/local/nginx/sbin/nginx同样先备份旧的。实际上线操作命令如下# 第一步查看当前master进程pid cat /usr/local/nginx/logs/nginx.pid # 第二步给旧master发送USR2信号启动新master kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid) # 第三步确认新master和worker已经启动 ps -ef | grep nginx执行kill -USR2后几秒内进程列表里会出现两套master进程和两套worker进程。注意此时nginx.pid文件已经被新master写入自己的pid而旧master的pid被保存在nginx.pid.oldbin里# 旧master的pid在新文件里 cat /usr/local/nginx/logs/nginx.pid cat /usr/local/nginx/logs/nginx.pid.oldbin继续执行优雅退出旧worker# 第四步给旧master发送WINCH信号关闭旧worker kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)发送WINCH后旧worker会陆续退出。你可以用ps -ef | grep nginx观察旧的worker数量逐步减少到0但旧master进程还在。此时新旧连接已经完全由新版本接管。最后收尾# 第五步确认新版本运行正常后退出旧master kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)执行完这五步在线升级就完成了。整个过程中业务连接没有中断对外服务的端口一直处于listen状态。3.3 新老进程共存期的行为与风险控制在线升级虽然不中断连接但新老进程共存的窗口期有一些微妙的行为不搞清楚容易出问题。第一这个窗口期内新老worker同时监听同一个端口内核会把新建连接随机分给新老worker。换句话说在发WINCH之前一部分新连接可能还是落在旧worker上由旧版本代码处理。如果你新版本改动很大想让所有请求尽快切换到新版本就尽快发送WINCH。第二旧worker退出需要时间。如果一个worker手上挂着一堆keepalive长连接它就迟迟不会退出。我遇到过挂着几百条HTTP长连接的情况WINCH发下去十几分钟那批worker还在。这时候不要急着QUIT旧master否则旧worker会被一并带走。但你也不能无限等下去。我个人的判断标准是核心业务的长连接已经退得差不多了、新版本没有产生新的error日志就可以QUIT旧master。剩下那些长连接等用户自然断开后旧worker自然会结束。第三如果新版本有问题怎么办。在没有执行QUIT之前回滚都是安全的方式如下# 向旧master发送HUP信号旧master会重新拉起worker kill -HUP $(cat /usr/local/nginx/logs/nginx.pid.oldbin) # 向新master发送QUIT信号关闭新版进程 kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid)HUP信号原本的含义是重载配置但它也可以让旧master重新fork新的worker。这样旧版本进程就重新接管了连接新版本优雅退出。注意千万不要在升级前手动删掉nginx.pid.oldbin在线升级的回滚判断全靠这个文件里的old master pid。有次我在临场操作时误以为pid文件是垃圾文件差点把它清理掉后来反应过来吓出一身冷汗。4. 升级后的验证清单、常见故障与回滚实操4.1 升级后的验证清单比curl -I更全的检查项很多人升级完nginxcurl -I看到200就觉得完事了。但实际上nginx这种基础组件的变化影响面是整个接入层验证要做得更立体。我自己整理了一份验证清单每次升级后照着过一遍检查项命令/方法预期结果版本确认/usr/local/nginx/sbin/nginx -v输出目标版本号配置正确性/usr/local/nginx/sbin/nginx -tsyntax is ok监听端口ss -lntp | grep nginx80/443等端口正常监听进程模型ps -ef | grep nginx1个masterN个worker无旧版本残留进程本地访问curl -I http://127.0.0.1/返回预期HTTP状态码HTTPS握手curl -vk https://127.0.0.1/TLS握手成功无证书告警站点批量访问用脚本遍历多个业务域名带Host请求全部返回预期状态静态资源压缩curl -H Accept-Encoding: gzip -I http://127.0.0.1/test.cssContent-Encoding: gzipHTTP/2验证curl --http2 -I https://127.0.0.1/能正常响应HTTP/2请求错误日志tail -f /usr/local/nginx/logs/error.log无新增error/warn级别日志访问日志字段对比升级前后一条访问日志字段格式一致或符合预期反向代理接口通过nginx访问一个后端接口并比对响应与升级前结果一致长连接稳定性用WebSocket客户端连接一段时间连接不中断、心跳正常这个清单看着多实际跑一遍也就几分钟。花这几分钟换来的是一整晚的安心。尤其是访问日志字段这一项我之前升级完没检查结果下游的日志采集分析任务当天就报错了——日志字段顺序变了。4.2 几个容易踩的坑从编译到运行期的教训这里把我在历次nginx升级中踩过或见过别人踩的坑集中说一下每一条都是真实教训。坑一编译参数和原来不一致功能静默丢失。这是最常见的坑。有人升级时只加了--with-http_ssl_module结果线上原本用着的--with-stream没了所有四层TCP/UDP代理全部不可用而错误日志里可能只会出现connect() failed之类的现象排查起来一头雾水。对策很简单升级前必须用nginx -V把原来参数原样抄下来一个都不能少。坑二systemd管理下的在线升级会被误杀。如果你的nginx是通过systemd服务启动的直接用kill -USR2做在线升级过程里可能出现一个诡异现象新master刚起来没几秒就被系统杀掉服务状态变成failed。原因在于systemd默认跟踪主进程PID而在线升级会改变PIDsystemd会认为主进程崩溃了于是把整个服务都干掉。处理方式有两种如果你一定要用systemd升级可以走systemctl restart nginx等于直接升级的重启版本会中断连接如果必须在线升级要配合systemd的Typeforking和PIDFile配置让systemd感知PID变化。建议升级前先确认自己服务器用的是裸进程管理还是systemd管理这个信息决定了你整个升级方案能不能按上面那套信号流程走。坑三tengine/OpenResty等衍生版本不能直接套nginx升级。如果你的nginx实际是tengine或者OpenResty直接下载官方nginx源码去替换二进制大概率会丢失大量自带module的编译选项甚至导致配置指令都无法识别。这类衍生版本请到它们自己的官网下载对应版本源码流程逻辑类似但不要用官方nginx源码包。坑四旧worker迟迟不退导致内存占用翻倍。在线升级期间新旧两套worker并存内存占用会短期上升。如果服务本身内存就很紧张这个过程可能触发OOM。建议升级前看一眼free -m内存不足时优先选择低峰期直接升级而不是强制在线升级。坑五依赖库升级后旧二进制不可用。假如你为了让新版本支持HTTP/3升级了系统的OpenSSL动态库升级完又因为业务原因回滚到旧nginx二进制这时旧二进制可能因为动态链接库不匹配而无法启动。这是我在一次回滚操作中亲身经历的情况。所以涉及动态库变更的升级回滚方案里必须包含对旧二进制可运行性的验证。4.3 直接升级与在线升级怎么选我的决策逻辑很多刚接触这块的朋友会纠结到底用哪种升级方式。我的建议很简单先问业务能不能承受秒级中断再问环境支不支持完整编译条件最后看自己是不是第一次做这类操作。场景推荐方式原因业务可接受短时中断 / 维护窗口直接升级简单可控、回滚直白长连接业务、7x24核心服务在线升级连接不断、无缝切换第一次升级、没有完整演练直接升级在线升级信号操作初看容易出错窗口期状态不好判断升级跨度大跨多个大版本直接升级方便逐步验证在线升级的新老配置差异更难控制systemd托管生产环境直接升级或systemctl restart避免信号操作与systemd管理机制冲突升级跨度大的场景我要多说一句如果你是从1.16直接升到1.26这种跨了五六个小版本的情况强烈建议先在一台测试机上模拟升级跑一遍配置检查和业务冒烟测试。小版本之间的配置差异积累起来很可能在某个点出现指令废弃或者默认行为变化直接在生产上操作风险不可控。4.4 升级不是一锤子买卖收尾和记录升级完成后最后一步是把现场收拾干净并留下记录。这步很多人会漏掉但运维的价值往往就体现在这种收尾动作里。首先确认nginx.pid.oldbin文件已经被清理旧master进程已经完全消失。然后更新自己的文档把当前nginx版本号、编译参数、升级时间、执行方式、验证结果记录到运维变更记录里。这样下次再升级时你手上就有了准确的nginx -V基线不用再临时去服务器上抓取。另外升级后在服务器本地保留一份新版本的源码包和编译产物不要急着删。我在实际工作中有一个习惯/usr/local/src/nginx-1.26.x这样带版本号的目录会保留至少一个版本周期。将来如果出现疑难问题可以直接回到源码目录查看当时的configure选项和编译环境这是排查问题的一个很重要的参照物。最后再分享一个我在多次升级中养成的习惯每次升级前把nginx -T大写的T输出完整配置导出一份存到本地。升级后的配置如果有任何异常变化用diff对比一下就知道差异在哪。这套组合拳下来nginx升级这件事就不需要再提心吊胆了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑