RedHat 9.0 这个版本在企业级服务器圈子里算是绕不开的一个存在,尤其是那些对系统稳定性和长期支持有硬性要求的场景,RHEL 9 几乎是默认选项。但问题也随之而来——系统自带的应用软件版本往往偏保守,比如 Nginx,官方仓库里提供的版本通常落后于主线好几个小版本,一些新特性用不上不说,某些模块的编译参数也未必符合实际业务需求。所以很多老运维在部署 Nginx 时,宁愿放弃dnf install nginx这条捷径,转而走源码编译安装的路子。这篇文章就是围绕“在 RedHat 9.0 上用源码方式安装 Nginx”这个主题,把我自己反复踩坑、反复验证之后的一套完整流程和思考逻辑摊开来聊。不管你是刚接触 RHEL 系发行版的新手,还是已经习惯用包管理器、想搞清楚源码编译到底多做了哪些事的老手,下面这些内容应该都能让你少走一些弯路。源码安装 Nginx 这件事,表面上看就是./configure && make && make install三步走,但真正决定成败的,是编译前的依赖梳理、编译参数的取舍,以及安装之后的系统集成——这三块任何一处偷懒,后面都会以各种奇怪的方式找上门来。
1. 为什么在 RedHat 9.0 上要折腾源码编译
1.1 包管理器装 Nginx 的局限到底在哪
很多人第一反应是:dnf一条命令就能搞定的事,为什么要费劲去编译?这个问题必须先说清楚,否则后面的操作就成了为了折腾而折腾。RedHat 9.0 的官方 AppStream 仓库里确实有 Nginx,但它的版本策略是“稳定优先、特性滞后”。官方仓库里的 Nginx 通常停留在某个较早的稳定分支,像 HTTP/3 支持、某些新版本的stream模块特性、以及较新的 OpenSSL 联动能力,在仓库版本里要么没有,要么需要额外打补丁。对于普通静态站点,仓库版本完全够用;但一旦涉及反向代理的细粒度调优、动态模块加载、或者需要针对特定硬件做编译优化,仓库版本就显得束手束脚。
另一个容易被忽略的点是编译参数的不可控。用dnf安装的 Nginx,其编译参数是打包者预设好的,你无法决定是否启用某个模块、无法调整--with-http_ssl_module之外的扩展、更没法把第三方模块(比如某些缓存增强或安全防护模块)编进去。源码编译的核心价值就在这儿——你拿到的是完全按自己需求裁剪过的二进制文件,装什么、不装什么,自己说了算。这在需要精简攻击面或者需要特定功能组合的生产环境里,差别非常明显。
还有一层现实考量是版本一致性。有些团队在多个环境里要求 Nginx 版本严格对齐,而不同 RHEL 小版本的仓库里 Nginx 版本可能不一致,源码编译反而成了保证一致性的手段。所以源码安装不是“高级玩法”,而是在特定约束下的理性选择。
1.2 源码编译带来的可控性与代价
源码编译最大的好处是可控,但可控从来不是免费的。代价主要体现在三个方面:一是依赖管理要自己扛,编译 Nginx 需要 PCRE、zlib、OpenSSL 这些库的开发包,缺一个就编译失败;二是升级维护要自己管,没有dnf update帮你处理版本迭代和安全补丁,每次升级都得重新编译、重新替换二进制;三是系统集成要自己做,包括创建专用运行用户、编写 systemd 服务单元、配置日志轮转,这些在包管理器方案里是自动完成的,源码方案里全得手动补齐。
我个人的判断标准很简单:如果只是跑个简单静态站或者轻量反代,仓库版本足够,没必要折腾;但如果你需要特定模块、需要控制编译优化选项、或者需要把 Nginx 和特定版本的 OpenSSL 绑定(比如为了统一的加密套件策略),那源码编译就是值得的。这篇文章的定位就是服务后者,同时把前者可能踩的坑也顺带讲清楚,让读者自己判断。
1.3 编译前的整体思路框架
在动手敲命令之前,脑子里得先有一张地图。整个源码安装流程可以拆成五个阶段:环境准备(依赖库和编译工具链)、源码获取与校验、configure 参数决策、编译与安装、系统集成与验证。这五个阶段里,最容易出问题的是第一阶段和第三阶段——依赖缺失导致的编译中断很常见,而 configure 参数选错导致的运行时功能缺失更隐蔽,往往要等到上线才发现某个模块没编进去。
我的习惯是先把依赖一次性装齐,再决定 configure 参数,最后才下载源码。这个顺序的好处是,参数决策时你已经清楚系统里有哪些库可用、版本是多少,不会出现“参数写了但库不支持”的尴尬。下面几个章节就按这个逻辑展开,每一步都说明白为什么这么做,而不只是告诉你做什么。
2. 编译环境与依赖库的完整准备
2.1 编译工具链的安装与确认
RedHat 9.0 最小化安装之后,gcc、make这些编译工具默认是不在的。第一步就是把工具链补齐。这里有个细节值得说:RHEL 9 默认的gcc版本是 11.x,对 Nginx 的编译完全够用,不需要额外升级。安装命令本身不复杂:
dnf groupinstall "Development Tools" -y dnf install -y gcc gcc-c++ make automake autoconf libtoolDevelopment Tools这个组包会把gcc、make、binutils等一整套工具拉进来,比逐个安装省事。装完之后建议验证一下版本:
gcc --version make --version我遇到过一种情况:系统里装了多个版本的gcc,configure阶段调用的和预期的不一致,导致编译出来的二进制在另一台机器上跑不起来。所以确认gcc的实际路径和版本是有必要的,which gcc看一眼路径,确保用的是系统默认的那套。
注意:不要为了追求“新版本”去手动替换系统
gcc,RHEL 9 的库依赖和gcc版本是配套的,贸然升级工具链可能引发其他编译问题。用系统自带的就够。
2.2 三大核心依赖库的取舍
Nginx 编译绕不开三个库:PCRE、zlib、OpenSSL。它们各自对应 Nginx 的不同能力,缺了哪个,对应的功能就编不进去或者直接编译失败。
PCRE 负责正则表达式支持,location里的正则匹配、rewrite规则、server_name的通配符匹配都依赖它。RedHat 9.0 仓库里默认提供的是 PCRE2,而 Nginx 对 PCRE2 的支持在较新版本里才完善。这里有个关键决策点:如果你用的 Nginx 版本较新,直接用系统的pcre2-devel即可;如果版本偏旧,可能需要单独编译 PCRE1。我的建议是优先用系统仓库的pcre2-devel,配合较新的 Nginx 稳定版,省去自己编译 PCRE 的麻烦。
zlib 负责 gzip 压缩,静态资源压缩、响应体压缩都靠它。这个库系统仓库里有zlib-devel,直接装就行,版本兼容性没什么坑。
OpenSSL 是最需要斟酌的一个。它决定了 Nginx 的 HTTPS 能力、TLS 版本支持、加密套件范围。系统仓库的openssl-devel版本跟随 RHEL 9 的更新节奏,通常够用。但如果你需要特定版本的 OpenSSL(比如为了统一某个加密策略),就得自己编译 OpenSSL 再用--with-openssl=指向它。大多数场景下,用系统openssl-devel是更稳妥的选择,自己编译 OpenSSL 会引入额外的维护负担。
安装命令:
dnf install -y pcre2-devel zlib-devel openssl-devel装完可以用rpm -q确认一下版本,心里有个数:
rpm -q pcre2-devel zlib-devel openssl-devel2.3 其他辅助依赖与目录规划
除了三大核心库,还有几个依赖按需安装。如果你打算启用--with-http_gzip_static_module,zlib 已经覆盖;如果要用--with-http_v2_module,不需要额外库;但如果涉及--with-http_perl_module,就需要perl-devel。另外,libxml2-devel和libxslt-devel在需要 XSLT 模块时才装。我的原则是:configure 参数里没打算启用的模块,对应依赖一律不装,保持系统干净。
目录规划这块,我习惯把源码放在/usr/local/src,安装到/usr/local/nginx,日志放/var/log/nginx,配置放/etc/nginx。这套布局和包管理器方案的路径接近,后续迁移或者写文档时不容易混淆。提前把目录建好:
mkdir -p /usr/local/src mkdir -p /var/log/nginx源码目录和安装目录分开,好处是升级时旧源码可以保留对照,出问题能快速回滚到上一版本的编译参数。
3. 源码获取、校验与 configure 参数决策
3.1 源码包的获取与完整性校验
Nginx 官方提供稳定版和主线版两个分支。生产环境我一般选稳定版(stable),主线版虽然特性新,但变动频繁,不适合求稳的场景。下载源码包时,务必同时下载对应的.asc签名文件或者核对官方公布的 SHA256 校验值。这一步很多人会跳过,但源码包的完整性直接关系到编译出来的二进制是否可信,尤其在安全要求高的环境里,校验不能省。
cd /usr/local/src # 以某个稳定版本为例,实际版本号以官方发布为准 curl -O https://nginx.org/download/nginx-<version>.tar.gz curl -O https://nginx.org/download/nginx-<version>.tar.gz.asc校验方式有两种:用sha256sum对比官方页面公布的哈希值,或者用gpg验证签名。前者简单直接,后者更严谨。我通常两个都做,sha256sum快速核对,gpg验证来源。校验通过后再解压:
tar -zxvf nginx-<version>.tar.gz cd nginx-<version>解压后的目录结构里,auto/存放的是 configure 脚本的各个检测模块,src/是源码,conf/是默认配置模板。花两分钟看一眼auto/options文件,能对可用的 configure 参数有个全局认识,比直接查文档更直观。
3.2 configure 参数的核心取舍逻辑
configure 参数是源码编译的灵魂,选错了要么功能缺失,要么二进制臃肿。我把参数分成三类来看:必选的基础参数、按需的功能模块、以及影响运行时的路径参数。
基础参数里,--prefix决定安装根目录,--sbin-path、--conf-path、--pid-path、--log-path这些决定各文件的落点。我习惯把它们显式写全,而不是只给--prefix让其余走默认,因为默认路径有时和系统约定不一致,后续写 systemd 单元时容易对不上。
功能模块方面,--with-http_ssl_module几乎是必开的,除非你确定这台机器只跑纯 HTTP。--with-http_v2_module现在也建议开,HTTP/2 对性能提升明显。--with-http_realip_module在反向代理场景下很有用,能拿到真实客户端 IP。--with-http_stub_status_module用于监控状态页,运维排障时很方便。--with-http_gzip_static_module支持预压缩文件直接返回,省 CPU。
反过来,有些模块默认不开、也确实用不上,就别加,比如--with-http_perl_module、--with-mail这些,除非业务明确需要。每多编一个模块,二进制就大一点,潜在的攻击面也多一点。
一个典型的 configure 命令长这样:
./configure \ --prefix=/usr/local/nginx \ --sbin-path=/usr/local/nginx/sbin/nginx \ --conf-path=/etc/nginx/nginx.conf \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/run/nginx.lock \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-pcre \ --with-threads--with-pcre让 Nginx 使用系统 PCRE 库,--with-threads启用线程池支持,对高并发下的磁盘 IO 有帮助。这些参数不是越多越好,而是每个都要能说出“为什么加”。
3.3 configure 输出解读与常见报错预判
configure跑完会输出一大段检测结果,很多人直接滚过去看最后一行有没有error。其实中间的信息更有价值——它会告诉你哪些库被找到、版本是多少、哪些模块被启用。我习惯把输出重定向到文件留档:
./configure [参数] 2>&1 | tee configure.log这样升级或者排查时能对照上一次的检测结果。常见的 configure 报错集中在依赖缺失上,比如提示找不到 PCRE library,多半是pcre2-devel没装或者路径不对;提示 OpenSSL 版本过低,说明系统openssl-devel版本不满足 Nginx 要求,这时要么升级系统包,要么自己编译 OpenSSL 并用--with-openssl=指定。还有一种隐蔽的报错是编译器特性检测失败,通常和gcc版本或CFLAGS设置有关,遇到时先看objs/autoconf.err里的详细日志,比盯着终端输出有用得多。
提示:configure 阶段如果报错,不要急着删目录重来,先看
objs/autoconf.err,里面记录了每个检测项的失败原因,定位效率高很多。
4. 编译、安装与系统集成实操
4.1 make 编译过程与并行度控制
configure 通过之后就是make。这一步的耗时取决于机器核数和启用的模块数量,一般几分钟到十几分钟不等。用-j参数并行编译能显著提速,但并行度不是越高越好。我的经验是设为 CPU 核数或者核数加一,比如 8 核机器用-j8或-j9。设太高反而会因为内存争抢导致编译变慢甚至 OOM。
make -j$(nproc)编译过程中如果出现 warning,大部分可以忽略,但涉及类型转换、隐式声明的 warning 值得留意,尤其是你自己加了第三方模块的时候。编译完成后不要急着make install,先确认objs/nginx这个二进制文件确实生成了,并且大小合理(通常几 MB)。可以用file objs/nginx看一眼架构和链接方式,确认没有链接到奇怪的库。
4.2 make install 与安装后目录核对
make install做的事就是把编译产物按 configure 里定的路径复制过去。执行完之后,重点核对几个位置:/usr/local/nginx/sbin/nginx是否存在且可执行,/etc/nginx/nginx.conf是否生成,/var/log/nginx目录权限是否正确。我见过因为--pid-path指向的目录不存在导致启动失败的案例,所以安装后手动确认一遍路径是值得的。
make install ls -l /usr/local/nginx/sbin/nginx ls -l /etc/nginx/如果 configure 时指定的某些路径目录不存在,make install不一定会自动创建,需要手动补。这一步的核对清单我整理成表格更清楚:
| 核对项 | 预期结果 | 常见问题 |
|---|---|---|
| 二进制文件 | /usr/local/nginx/sbin/nginx存在且可执行 | 路径拼写错误导致落点不对 |
| 主配置文件 | /etc/nginx/nginx.conf存在 | 目录不存在导致安装中断 |
| 日志目录 | /var/log/nginx存在且属主正确 | 权限不足导致启动写日志失败 |
| PID 目录 | /var/run可写 | 部分加固系统限制了该目录写入 |
4.3 运行用户、systemd 单元与日志轮转
源码安装最容易漏掉的就是系统集成。包管理器方案里,nginx用户、systemd 服务、logrotate 配置都是现成的,源码方案得自己补。
首先是运行用户。Nginx 主进程通常以 root 启动(为了绑定 80/443 端口),worker 进程降权到专用用户。创建一个不可登录的系统用户:
useradd -r -s /sbin/nologin -d /usr/local/nginx -M nginx然后在nginx.conf里把user指令设为nginx。注意 worker 进程需要对日志目录、缓存目录有写权限,属主设置要对应上。
接着是 systemd 单元。在/etc/systemd/system/nginx.service写入服务定义,关键点包括ExecStartPre做配置检查、ExecReload用-s reload、PIDFile和 configure 里的--pid-path保持一致。写完之后systemctl daemon-reload再systemctl enable --now nginx。这里有个细节:Type用forking,因为 Nginx 默认以守护进程方式运行,用simple会导致 systemd 误判启动状态。
日志轮转用 logrotate 配置,放在/etc/logrotate.d/nginx,设置按天切割、保留份数、切割后给主进程发USR1信号重新打开日志文件。不发信号的话,切割后的新日志不会生效,旧文件句柄一直被占用。
4.4 启动验证与基础功能自测
服务起来之后,验证不能只看systemctl status显示 active。我一般分三层验证:进程层看ps确认 master 和 worker 都在;端口层用ss -tlnp确认 80/443 在监听;应用层用curl -I请求本地,看返回头和状态码。如果启用了stub_status,再访问一下状态页确认连接数统计正常。
systemctl status nginx ss -tlnp | grep nginx curl -I http://127.0.0.1/三层都通过,才算安装真正完成。只看到 active 就收工,后面很可能在真实流量进来时才发现问题。
5. 常见问题排查与升级维护经验
5.1 编译与启动阶段的典型故障
源码安装的故障大致分两类:编译期和运行期。编译期最常见的是依赖缺失,报错信息里通常带not found或者No such file,对照 configure 日志里的检测项就能定位。另一类是版本不匹配,比如 Nginx 版本和 PCRE2 的兼容问题,表现为 configure 通过但运行时报正则相关错误。
运行期故障里,启动失败最常见的原因是端口被占用或者权限不足。80 端口被其他服务占了,ss -tlnp一看便知;权限问题多见于 worker 用户对日志目录或临时目录没有写权限,error.log里会有明确的Permission denied。还有一种隐蔽情况是 SELinux 拦截,RHEL 9 默认启用 SELinux,源码安装的 Nginx 如果监听非标准端口或者访问非标准目录,会被策略挡住。排查时先看/var/log/audit/audit.log,确认是 SELinux 问题后,用semanage port或调整文件上下文来解决,而不是直接关 SELinux。
我把常见问题整理成速查表:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| configure 报找不到 PCRE | pcre2-devel未装或路径不对 | 检查 rpm 包与--with-pcre参数 |
| make 中途失败 | 内存不足或依赖头文件缺失 | 降低-j并行度,检查-devel包 |
| 启动报 bind 失败 | 端口被占用 | ss -tlnp查占用进程 |
| 启动报权限拒绝 | worker 用户无目录写权限 | 检查日志、缓存目录属主 |
| 访问返回 403 | 静态文件路径或 SELinux 上下文问题 | 查error.log与 audit 日志 |
| reload 后配置未生效 | 配置文件语法错误或信号未送达 | nginx -t检查语法 |
5.2 版本升级时的平滑替换思路
源码安装的升级没有dnf update那么省心,但也不是没法平滑做。思路是:下载新版本源码,用和旧版本一致的 configure 参数编译(参数从旧版本的configure.log或者nginx -V输出里拿),编译出新二进制后,先备份旧二进制,再用新二进制替换,最后nginx -t检查配置、systemctl reload nginx平滑重载。整个过程服务不中断,因为 reload 是优雅重载,旧 worker 处理完存量请求才退出。
nginx -V这个命令在升级时特别有用,它会把编译时的完整参数打印出来,照着它重新 configure 就不会漏参数。我习惯每次编译后把nginx -V的输出存一份到文档里,升级时直接对照。
5.3 几个我踩过的坑和实操心得
第一个坑是--pid-path和 systemd 的PIDFile不一致。有次升级后忘了同步改 systemd 单元,结果systemctl stop停不掉进程,只能手动 kill。教训是这两个路径必须成对维护,改一个就得改另一个。
第二个坑是日志目录权限。源码安装后我图省事直接chmod 777,后来安全扫描直接报高危。正确做法是把目录属主设为 worker 用户,权限 755 或 750,主进程以 root 写日志没问题,worker 只需要读或者特定子目录写。
第三个心得是关于 configure 参数的版本管理。我现在的做法是把 configure 命令写成一个build.sh脚本,和源码包放一起,升级时改个版本号就能复用。这样既避免了参数遗漏,也让整个编译过程可复现,换台机器照样能编出一致的二进制。
提示:每次编译完,把
nginx -V输出、configure 命令、依赖包版本清单三样东西归档,升级和排障时能省大量时间。
这套流程我在几台 RHEL 9 的机器上反复跑过,从最小化安装到服务上线,顺利的话半小时内能完成。真正花时间的不是敲命令,而是想清楚每个参数为什么加、每个目录为什么这么放。把这些想明白了,源码安装就从“折腾”变成了“可控”,后面无论是升级还是排障,心里都有底。