资讯动态

Linux下Nginx源码编译安装与systemd平滑升级实战

发布时间:2026/10/27 4:16:57 来源:尧图企业网站定制
线上机器半夜告警一台边缘节点的 502 率突然抬头登上去一看跑着的 Nginx 是发行版仓库里带的那个老版本编译时压根没编进http_v2和realip前面套了一层负载均衡之后日志里全是内网地址排查等于闭着眼睛摸。那晚之后我把所有对外服务节点的 Nginx 全部换成了自己编译的版本——不是为了炫技纯粹是仓库里的二进制包满足不了这条业务链。这篇就围绕 Linux 系统下 Nginx 源码安装这件事把我这些年从./configure敲到make install、再到 systemd 接管、再到线上平滑换二进制的一整套流程捋一遍重点讲清楚每个参数为什么这么写、哪一步最容易翻车、翻车之后怎么定位。看完你至少能做到在一台干净的机器上从零编译出一个路径统一、模块可控、能被 systemd 正常托管的 Nginx并且知道出问题时该从哪里下手。1. 包管理器一行命令的事为什么还要折腾源码编译很多人第一次听到源码安装第一反应是麻烦。确实apt install nginx或者yum install nginx三秒钟搞定配置文件位置、日志位置、启动脚本全部给你安排明白。但真到了生产环境仓库包的问题会一个接一个冒出来。1.1 三个真实约束把选择逼向编译我碰到的第一个约束是版本滞后。发行版为了保证稳定性仓库里的软件版本往往落后主线一到两年。有次需要用到stream模块里某个较新的特性做四层转发仓库版本根本没有只能自己编。第二个约束是模块裁剪。发行版打出来的包为了兼容性会把一堆用不上的模块编进去同时又有一些社区模块比如某些第三方缓存、限流模块它不可能给你带。而真正影响性能的--with-http_v2_module、--with-http_realip_module、--with-http_gzip_static_module这些反而要看你装的包有没有开。第三个约束是路径统一。仓库包的默认路径散落在/etc/nginx、/var/log/nginx、/usr/share/nginx、/var/lib/nginx好几个地方做容器镜像或者批量部署的时候挂载和备份都特别别扭。自己编译可以全部收进一个--prefix根目录下迁移的时候整个目录打包带走干净利落。注意如果你的场景只是内网小工具、临时测试、学习环境别折腾直接用包管理器。源码编译的价值在可控性不在更高级。1.2 源码安装与二进制包的取舍对照维度包管理器安装源码编译安装上手成本极低一条命令需要准备依赖、管理编译链版本新鲜度跟随发行版通常滞后可锁定任意稳定版模块控制固定组合第三方模块难加完全按需裁剪路径规划分散在系统标准位置全部收敛到 prefix升级方式包管理器一键升级替换二进制 平滑重载补丁追踪发行版回填安全补丁需要自己盯版本和补丁适合场景常规业务、快速验证定制化需求、性能敏感、镜像构建这张表里最后一行补丁追踪是源码安装最容易被忽略的代价。仓库包虽然版本旧但发行版维护者会把安全补丁回填到旧版本上你自己编译的主线版本出了安全问题得自己跟进升级。所以我的习惯是编译参数和版本号记在一个固定文件里每次升级只改版本号重新编不动参数。1.3 编译前先想清楚这次装的是一次性还是长期维护这一步很多人跳过结果装了三次三次路径都不一样最后机器上同时躺着/usr/local/nginx、/opt/nginx、/data/app/nginx三份东西谁也说不清哪个在用。我的做法是在动手前先定三件事安装根目录、运行用户、日志落点。根目录我一般选/usr/local/nginx或者/opt/nginx后者更适合和业务程序放在一起运行用户统一用nginxuseradd -r -s /sbin/nologin nginx建好日志落点跟着根目录走logs/下面按access.log、error.log分。这三件事定下来后面所有参数都是围绕它们展开的不会乱。另外要确认编译机本身的底子gcc或者clang有没有、make版本够不够、pkg-config装没装。这些在最小化安装的系统镜像里经常是缺失的gcc缺了会在configure阶段直接报C compiler cc is not found看着吓人其实就是没装编译器。2. 依赖库这一关PCRE、zlib、OpenSSL 的版本账要提前算Nginx 本身的源码不含正则引擎、不含压缩算法、不含 TLS 实现它是调用外部库的。所以源码安装真正的难点不在 Nginx 自己而在这三个依赖上。我见过太多人卡在这一步报错信息看起来跟 Nginx 毫无关系。2.1 PCRE2 与 PCRE1一个容易忽略的分水岭location里的正则匹配、rewrite指令、map里的正则全靠 PCRE 提供支持。较新的 Nginx 版本已经支持 PCRE2但如果你想用一些还在依赖 PCRE1 的第三方模块就得注意版本匹配。系统里判断装没装用这两条命令pcre2-config --version pcre-config --version前者是 PCRE2后者是 PCRE1。如果两条都没输出说明两个都没装。Debian 系一般是libpcre2-devRHEL 系是pcre2-devel。装了之后configure会自动探测你也可以显式指定--with-pcre让它用自带的源码编译版本适合目标机器不允许装开发包的情况。这里有个坑开发包和运行库是两个包。libpcre2-8-0是运行库libpcre2-dev才是头文件。只装前者configure阶段会报the HTTP rewrite module requires the PCRE library因为找不到pcre2.h。这个报错措辞很误导让人以为 PCRE 完全没装其实是头文件缺了。2.2 OpenSSL 的版本链条与 ABI 陷阱要开 HTTPS就得有 OpenSSL。这里的问题比 PCRE 复杂得多因为 OpenSSL 3.x 和 1.1.x 之间的 API 变化不小而不同 Nginx 版本对 OpenSSL 3.x 的支持程度又不一样。用太新的 OpenSSL 编太老的 Nginx编译期可能直接报一堆undefined reference。我的经验是Nginx 主线稳定版搭配 OpenSSL 3.x 不会有问题但如果你的版本比较老优先用 OpenSSL 1.1.1 系列。判断方法很简单先看 Nginx 源码目录下的auto/lib/openssl/conf里面会写明它期望的 API更省事的办法是直接看官方发布说明里提到的兼容版本。还有一种情况是目标机器上装了多个 OpenSSL系统自带的在/usr/lib64你自己编的在/usr/local/ssl。这时候configure可能链接到错误的那一份编出来的 Nginx 在运行时加载了另一份库表现出来就是启动时报version OPENSSL_3.0.0 not found。解决办法是在configure时明确指定--with-openssl/usr/local/src/openssl-3.0.x \ --with-openssl-opt--openssl-no-asm--with-openssl指向的是 OpenSSL源码目录不是安装目录Nginx 会用它自己的构建流程把这个库静态编进去。这么做的好处是最终二进制不依赖系统里的 OpenSSL 动态库迁移到别的机器上不会因为库版本不一致而崩。2.3 zlib 与那些看起来装了其实没装的假象zlib 负责 gzip 压缩gzip on、gzip_static on这些指令背后都是它。判断方法ls /usr/include/zlib.h rpm -qa | grep zlib # RHEL 系 dpkg -l | grep zlib # Debian 系同样的问题zlib这个包名在 RHEL 系里指的是运行库开发包叫zlib-devel。Debian 系对应zlib1g和zlib1g-dev。只装前者configure会报the HTTP gzip module requires the zlib library。2.4 依赖自检的落地命令动手编译之前我习惯跑一遍这套自检五秒钟确认底子齐不齐for pkg in gcc make pcre2-devel zlib-devel openssl-devel; do rpm -q $pkg 2/dev/null || echo 缺少: $pkg doneDebian 系把rpm -q换成dpkg -s。如果目标机器联网不便可以把这些开发包提前下载好用本地源安装。还有个小细节--with-http_v2_module需要 OpenSSL 支持 ALPNOpenSSL 1.0.2 之前的版本没有这个能力编进去也跑不起来 HTTP/2。所以如果你的业务需要 HTTP/2OpenSSL 的版本下限就是 1.0.2实际建议 1.1.1 起步。3. ./configure 那串长参数每一个都在决定以后的运维成本configure是整件事的核心。很多人是从别处抄一长串参数过来能编过就行至于每个参数干什么、以后升级时能不能删一概不知。等到某天要加个模块发现参数早就丢了只能重新摸索。我下面按功能分组讲每个参数都说清楚它影响什么。3.1 路径参数把配置文件、日志、PID 全部收进一个根目录--prefix是总开关它决定了其他所有相对路径的基准。我一般写--prefix/usr/local/nginx。但光有它不够因为 Nginx 有一些路径默认不跟着 prefix 走必须单独指定--sbin-path/usr/local/nginx/sbin/nginx --conf-path/usr/local/nginx/conf/nginx.conf --pid-path/usr/local/nginx/logs/nginx.pid --lock-path/usr/local/nginx/logs/nginx.lock --error-log-path/usr/local/nginx/logs/error.log --http-log-path/usr/local/nginx/logs/access.log --http-client-body-temp-path/usr/local/nginx/temp/client_body --http-proxy-temp-path/usr/local/nginx/temp/proxy --http-fastcgi-temp-path/usr/local/nginx/temp/fastcgi --http-uwsgi-temp-path/usr/local/nginx/temp/uwsgi --http-scgi-temp-path/usr/local/nginx/temp/scgi这几个temp-path特别值得说。Nginx 处理大请求体、代理大响应时会把内容先落到临时文件里默认位置在prefix下的client_body_temp之类目录。如果你的系统盘小、数据盘大就应该把这些临时目录指到大盘上否则上传大文件时容易把根分区写满。这个坑我在早期项目里踩过一次一个 4G 内存的机器上跑了 20 个上传接口某天根分区 100%Nginx 报No space left on device一查全是临时文件。提示这些 temp 目录 Nginx 自己不会创建make install之后必须手动mkdir -p出来否则启动时报mkdir() /usr/local/nginx/temp/client_body failed。3.2 模块参数哪些必须显式打开哪些默认就有Nginx 的模块分两类默认编进去的不带--with-前缀也能用和需要显式开启的。默认就有的包括http_core、http_access、http_auth_basic、http_autoindex、http_proxy、http_rewrite等一大批基础模块。需要显式打开的我列几个几乎必开的--with-http_ssl_module不开就没有 HTTPS这是最常见的漏项。--with-http_v2_moduleHTTP/2 支持现代站点的标配。--with-http_realip_module上游是 CDN 或负载均衡时用它还原真实客户端 IP日志才有意义。--with-http_stub_status_module暴露一个简单的状态页能看连接数、请求数做监控的基础。--with-http_gzip_static_module支持直接返回预压缩好的.gz文件配合前端构建产物效果很好。--with-stream四层转发做 TCP/UDP 代理时需要。--with-threads线程池支持处理大量静态文件或者磁盘 IO 慢的场景有收益。另外还有一类是禁用参数--without-http_autoindex_module、--without-http_ssi_module这种。我一般会砍掉用不上的模块二进制体积能小一些攻击面也小一些。但要注意别砍过头--without-http_rewrite_module砍掉之后所有rewrite都不生效排查起来很费时间。3.3 指向第三方源码的 --with-xxx 参数怎么用前面说过--with-openssl指向源码目录的用法。--with-pcre和--with-zlib同理都是指向解压后的源码目录让 Nginx 在编译过程中顺带把依赖静态编进去。这么做的好处是最终二进制自包含坏处是每次升级依赖都要重新编 Nginx。我的取舍原则如果目标机器是自己长期维护的用系统库方便升级如果是做镜像分发、要跨机器复制二进制用静态编译省事。还有--with-cc-opt和--with-ld-opt两个参数用来往编译器和链接器里塞额外选项。常见的用法是--with-cc-opt-O2 -fstack-protector-strong -D_FORTIFY_SOURCE2 \ --with-ld-opt-Wl,-z,relro -Wl,-z,now这几个是加固编译选项启用栈保护和只读重定位属于成本极低、收益明确的加固手段。如果你的机器上装了自定义路径的库也在这里加-I和-L。3.4 一份可以直接抄的 configure 清单把上面几组合起来我常用的完整参数是这样./configure \ --prefix/usr/local/nginx \ --sbin-path/usr/local/nginx/sbin/nginx \ --conf-path/usr/local/nginx/conf/nginx.conf \ --pid-path/usr/local/nginx/logs/nginx.pid \ --error-log-path/usr/local/nginx/logs/error.log \ --http-log-path/usr/local/nginx/logs/access.log \ --http-client-body-temp-path/usr/local/nginx/temp/client_body \ --http-proxy-temp-path/usr/local/nginx/temp/proxy \ --http-fastcgi-temp-path/usr/local/nginx/temp/fastcgi \ --http-uwsgi-temp-path/usr/local/nginx/temp/uwsgi \ --http-scgi-temp-path/usr/local/nginx/temp/scgi \ --usernginx \ --groupnginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-stream \ --with-threads \ --with-cc-opt-O2 -fstack-protector-strong \ --with-ld-opt-Wl,-z,relro -Wl,-z,now把这段存成一个build.sh以后升级只改源码版本号参数不动。这是我这些年最省事的一个习惯比在笔记里东拼西凑强得多。4. make 到 make install 之间文件落点、权限与目录预建configure通过之后会打印一屏摘要包括安装路径、模块列表、OpenSSL 版本。别急着敲make先花十秒扫一眼这份摘要确认--prefix对了、SSL 模块在里面、编译器是预期的那个。我确实见过configure用错了交叉编译器的场景摘要里gcc那一行写着aarch64-linux-gnu-gcc而目标机器是 x86编出来的二进制根本跑不了。4.1 编译并发数与内存的换算make默认单线程编一个 Nginx 大概要一两分钟。加-j可以并编但并发数不是越大越好。GCC 每起一个编译进程大概要吃 200 到 400 MB 内存4G 内存的机器-j8就可能被 OOM Killer 干掉表现出来是make: *** [objs/...] Killed看着像编译错误其实是内存不够。我的经验公式是并发数 min(CPU 核数, 可用内存 GB 数 / 0.5)。8G 内存 4 核的机器-j4稳妥16G 8 核-j8没问题。编译期间用free -h盯一眼swap 开始大量使用就说明并发开大了。4.2 安装后必须补建的目录和属主make install会把二进制、配置、基础目录装好但前面说的那些 temp 目录不会自动创建。装完之后第一件事mkdir -p /usr/local/nginx/temp/{client_body,proxy,fastcgi,uwsgi,scgi} chown -R nginx:nginx /usr/local/nginx/temp chown -R nginx:nginx /usr/local/nginx/logs另外logs目录的属主也容易被忽略。Nginx 的 master 进程以 root 启动worker 进程以nginx用户跑。error log 是 master 打开的access log 是 worker 打开的如果logs目录属主是 root 且权限是 755worker 写 access log 就会失败。表现出来是访问正常但日志不涨排查的时候容易怀疑到别的地方去。稳妥做法是logs目录属主给nginxmaster 以 root 启动照样能写。4.3 配置文件的瘦身与模块加载方式编译完之后/usr/local/nginx/conf/nginx.conf是官方示例里面注释很多worker_processes写的是 1server块是个欢迎页。我一般直接替换成自己的模板worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 10240; use epoll; multi_accept on; } http { include mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; keepalive_timeout 65; server_tokens off; 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 logs/access.log main; error_log logs/error.log warn; include conf.d/*.conf; }worker_processes auto让 Nginx 自己按 CPU 核数决定比手写数字可靠。worker_rlimit_nofile要和系统的ulimit -n对齐否则可能报Too many open files。use epoll在 Linux 上是默认的写出来只是为了明确。include conf.d/*.conf是我加的一层业务站点配置全放conf.d下面主配置不动方便按站点增删。提示make install每次都会覆盖conf目录下的文件。如果你改过nginx.conf又执行了一次make install改动就没了。升级前先把conf备份一份这是个很小但很致命的习惯。5. 让源码装的 Nginx 融入 systemd 与平滑升级流程源码装的 Nginx 没有 systemd unit 文件只能sbin/nginx手动起。这在测试环境无所谓生产环境必须交给服务管理器不然开机自启、崩溃拉起、日志收集全部要自己写脚本。5.1 手写 unit 文件的几个关键字段在/etc/systemd/system/nginx.service写这么一份[Unit] Descriptionnginx - high performance web server Afternetwork-online.target remote-fs.target nss-lookup.target Wantsnetwork-online.target [Service] Typeforking PIDFile/usr/local/nginx/logs/nginx.pid ExecStartPre/usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf ExecStart/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf ExecReload/usr/local/nginx/sbin/nginx -s reload ExecStop/bin/kill -s QUIT $MAINPID PrivateTmptrue LimitNOFILE65535 Restarton-failure RestartSec3 [Install] WantedBymulti-user.target几个字段值得解释。Typeforking是因为 Nginx 默认以守护进程方式启动systemd 要等它 fork 出子进程之后才算启动完成同时靠PIDFile找到主进程。ExecStartPre里的nginx -t是关键配置语法错了直接启动失败不会出现进程起来了但配置是坏的这种状态。LimitNOFILE必须设置否则 systemd 给进程设的默认文件描述符上限可能只有 1024跟配置里的worker_rlimit_nofile对不上。写完执行systemctl daemon-reload systemctl enable --now nginx然后systemctl status nginx确认Active: active (running)。如果状态是failed先看journalctl -u nginx -n 50再对着 Nginx 自己的 error log 看两边信息是互补的。5.2 二进制替换式升级的完整节奏源码装的 Nginx 升级本质是换掉 sbin 目录下的二进制然后让老的 master 进程优雅退出。最省事的方式是用-s reload但那只重载配置不换二进制。真正换二进制要走信号流程# 1. 编译新版本到临时位置 make install DESTDIR/tmp/nginx-new # 2. 备份老二进制 cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old # 3. 替换 cp /tmp/nginx-new/usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx # 4. 语法校验 /usr/local/nginx/sbin/nginx -t # 5. 触发平滑升级 kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid) # 6. 老 worker 优雅退出 kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)USR2信号让老 master 把 pid 文件改名为nginx.pid.oldbin然后启动一个新的 master 和一批新 worker此时新旧两套进程同时存在老连接继续由老 worker 处理。等老 worker 把存量请求处理完再发QUIT让它退出。这中间如果发现新版本有问题发HUP给老 master 就能回滚整套流程对线上是零中断的。我自己的习惯是把这套流程写成一个脚本脚本里加一步nginx -V对比新旧二进制编译参数参数不一致就直接中止。原因很简单换版本的时候最容易出的问题是编译参数被改某个模块没了重启之后才发现某个站点 404 或者 HTTPS 握手失败。5.3 把编译参数留档nginx -V 的价值nginx -V会打印出编译时的全部参数这是排查线上问题的第一手信息。我见过很尴尬的场面线上出问题想知道有没有开realip登上去nginx -V结果发现这一行被截断了参数太多。所以除了看输出我还会在编译完成后做两件事/usr/local/nginx/sbin/nginx -V 2 /usr/local/nginx/conf/build-info.txt同时把build.sh也放到conf/目录里。这两个文件加起来就是这台机器上 Nginx 的身份证明出问题时对照着看比翻部署文档靠谱得多。6. 编译与启动阶段的六类高频故障及排查链路这一节我把这些年踩过的坑按阶段列出来每个坑给出完整排查链路而不是只给结论。因为同样的报错可能有好几种原因直接抄答案容易抄错。6.1 configure 阶段报找不到库最典型的报错是./configure: error: the HTTP rewrite module requires the PCRE library。排查顺序应该是这样先ls /usr/include/pcre2.h确认头文件在不在再ldconfig -p | grep pcre2确认运行库在不在最后看configure输出里那一行checking for PCRE2 library ... not found后面跟着的搜索路径。如果库装在了非标准路径比如/opt/pcre需要在--with-cc-opt里加-I/opt/pcre/include在--with-ld-opt里加-L/opt/pcre/lib。另一个容易误判的是C compiler cc is not found。这条不是库的问题是编译器没装。gcc -v确认一下装gcc和make就行。如果装了还报看看是不是PATH里没有/usr/bin这种在精简镜像里偶尔会出现。6.2 make 阶段编译器层面的报错编译过程中最常见的两类错误一类是error: xxx undeclared通常是 OpenSSL 版本和 Nginx 版本不匹配老 Nginx 用了新 OpenSSL 里被移除的 API。解决办法是降 OpenSSL 版本或者升 Nginx 版本没有第三条路。另一类是undefined reference to SSL_xxx链接阶段找不到符号说明头文件和库文件版本不一致——编译用了一套头文件链接用了另一套库。这种在机器上同时装了多个 OpenSSL 的时候特别容易出现。ldd objs/nginx | grep ssl能看出它最终链的是哪个库。还有一类看着像错误其实不是make: warning: Clock skew detected。这是文件时间戳比系统时间还新通常发生在虚拟机时间同步出问题之后。make clean重来一次就行不影响正确性但如果是正式构建建议先把时间同步修好避免增量编译的依赖判断出错。6.3 装完了却起不来用户、端口、目录权限nginx: [emerg] getpwnam(nginx) failed这条是运行用户不存在。id nginx确认一下没有就useradd -r -s /sbin/nologin nginx。注意--usernginx这个参数是编译时写死在二进制里的并不是运行时读配置所以用户必须在编译前就存在否则即使后来建了用户二进制里记录的还是那个 uid。这种情况要重新configure再编一次。nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)是端口权限问题。Linux 上 1024 以下的端口需要 root 或者CAP_NET_BIND_SERVICE能力。Nginx 本身设计就是 master 以 root 启动、worker 降权所以正常应该是sudo或者 systemd 启动。如果是非 root 用户直接跑sbin/nginx就会撞这个报错。nginx: [emerg] mkdir() /usr/local/nginx/temp/client_body failed就是前面说的 temp 目录没建。nginx: [emerg] open() /usr/local/nginx/logs/error.log failed (13: Permission denied)是日志目录权限问题chown -R nginx:nginx logs解决。6.4 起来了但行为不对配置生效顺序与包含关系有一种情况是服务启动正常但改的配置不生效。排查思路是先nginx -t确认语法再nginx -T打印最终合并后的完整配置看你的修改有没有被别的include覆盖。-T这个参数我用了很多年才发现比只看主配置文件高效太多尤其是配置分层比较多的项目。另一种是改完配置reload了但没生效检查nginx.pid里的进程号是不是你 reload 的那个。机器上跑了多个 Nginx 实例时-s reload默认读--pid-path指定的文件如果实例用的是不同配置文件不同 pid 文件就得加-c和-p参数明确指定。6.5 升级之后模块缺失或行为变化平滑升级之后出现某个 location 返回 404或者HTTPS 握手失败八成是编译参数变了。nginx -V对比新旧两版的参数看--with-http_ssl_module还在不在、--with-openssl指向的路径变没变。我还会顺手比一下nginx -V里的built with OpenSSL那一行版本号OpenSSL 版本变了之后 TLS 协议和加密套件的默认值可能跟着变老客户端可能就连不上了。ssl_protocols这个配置也值得在升级后检查一遍。OpenSSL 3.x 默认禁用了 TLS 1.0 和 1.1如果你的配置里还写着ssl_protocols TLSv1 TLSv1.1 TLSv1.2;启动时会报SSL_CTX_set_cipher_list相关的错误或者直接忽略掉不支持的协议表现出来就是老客户端连不上但日志里没什么明显报错。6.6 性能相关的隐性故障有次线上换完编译版本QPS 没变但 CPU 使用率高了一截。查了半天发现是编译时漏了--with-threads而这个业务场景大量依赖aio threads做静态文件读取配置里写了aio threads但模块没编进去Nginx 退化成同步读磁盘 IO 把 CPU 拖住了。这类问题的排查依靠两样东西一是nginx -V里确认模块在不在二是error.log里有没有类似aio directive is not allowed here或者unknown directive的警告。unknown directive这个报错很有迷惑性配置里写得好好的一启动就报未知指令答案就在模块没编进去这五个字上。7. 把这些年攒下来的几个细节再说透一点前面几节讲的是主线流程这一节补充一些零散但真正影响成败的细节都是我反复踩过之后固化下来的做法。关于编译用户。不要用 root 直接跑./configure make。倒不是安全问题那么严重主要是 root 编出来的文件属主是 rootmake install之后所有文件也都是 root 的后续升级、改配置都要 sudo很别扭。更麻烦的是某些构建脚本会读取$HOME下的缓存root 和普通用户的缓存混在一起增量编译偶尔会出怪问题。我的做法是用普通用户编译make install那一步才 sudo。关于源码目录的选择。源码放在/usr/local/src/下面解压出来的目录名带着版本号比如nginx-1.24.0。升级的时候新版本解压到旁边两个目录并存方便回滚。编译产物objs/目录会比较大编完确认没问题之后可以make clean清掉只保留二进制和文档。关于磁盘空间。Nginx 源码本身不大十来兆但objs/目录编完能到几十兆加上 OpenSSL 源码编出来的中间产物整个构建过程占用 200 到 500 MB 的临时空间。/usr/local/src单独挂一个小分区的时候要注意No space left on device在编译阶段出现报错位置可能很靠后容易误判成代码问题。关于配置文件里的user指令。编译时指定了--usernginxnginx.conf里那行user nobody;就不要再留着了它会覆盖编译时的默认值。我见过配置里还写着user nobody;结果 worker 以 nobody 跑logs目录是 nginx 属主access log 死活写不进去。这种历史遗留配置在迁移的时候特别容易被带过去改配置之前先grep -n ^user /usr/local/nginx/conf/nginx.conf确认一下。关于日志切割。源码装的 Nginx 不会自动切日志需要自己配 logrotate。我用的模板是这样/usr/local/nginx/logs/*.log { daily rotate 14 missingok notifempty compress delaycompress sharedscripts postrotate [ -f /usr/local/nginx/logs/nginx.pid ] kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid) endscript }关键是postrotate里那个USR1信号它让 Nginx 重新打开日志文件。少了这一步日志文件被 rotate 走了Nginx 还握着旧的文件描述符新日志会写到已被移动的文件里磁盘空间不会释放du和df对不上看起来像磁盘泄漏。关于nginx -t的使用时机。不只是改完配置之后要跑reload之前更要跑。nginx -t只做语法和基本逻辑检查不检查端口占用、不检查上游可达性所以它通过了不代表 reload 一定成功。但反过来它不通过就一定有问题属于成本极低的拦截。把它放进ExecStartPre里等于每次启动前自动做了这道检查。关于二进制体积。全模块编出来的 Nginx 大概十几兆裁剪之后能到一两兆。做容器镜像的时候这个差距在多层缓存下会被放大。我的做法是保留必要模块砍掉mail、http_perl、http_dav这些用不上的配合strip处理一下最终能压到三四兆。strip的命令是strip /usr/local/nginx/sbin/nginx但要注意 strip 之后nginx -V的输出还在因为它存在二进制的字符串段里不受影响。关于回滚预案。每次升级前我把老二进制备份成nginx.$(date %Y%m%d)配置目录整体tar一份到/usr/local/nginx/backup/。真出事的时候cp回去kill -HUP主进程几十秒恢复。这套动作我演练过几次比现场翻文档快得多。回滚这件事准备的时候觉得多余用上的时候觉得庆幸我属于后者。最后说一个感受源码安装 Nginx 这件事技术难度其实不高真正难的是一致性——每台机器的参数一致、路径一致、升级流程一致。把build.sh、unit 文件、logrotate 配置、备份脚本这几样东西固化下来形成一个可以复制的最小单元后面再装多少台都是机械动作。我现在的做法是把这个单元打包成一个内部脚本新机器一条命令跑完跑完打印一份摘要包括版本、模块列表、路径、运行用户贴到部署记录里就算完事。这套东西谈不上多高级但确实让我少加了不少夜班。

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

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

免费获取报价 →
↑