☰
Linux离线安装nginx:源码编译、依赖修复与systemd托管实战
2026/9/29 5:10:24 网站建设 项目流程

1. 断网环境下装一个nginx,难的不是nginx本身

很多人第一次遇到"Linux系统离线安装nginx"这个需求时,心里的第一反应是:nginx不就一个压缩包吗,拷进去解压编译不就完事了。真到了那台机器前面,敲下./configure的那一刻,屏幕上跳出来的一串not found才会让人意识到,事情没那么简单。我在几个完全物理隔离的内网机房里做过这套动作,从x86_64到ARM64,从CentOS 7到较新的国产化操作系统发行版都趟过一遍,结论很明确:离线安装nginx的难度从来不在nginx本身,而在于你要把一个完整的构建与运行环境,在没有任何外网连接的前提下重建出来。

这篇文章面向的是这样一类人:手上有一台只能通过U盘、内网跳板或者文件摆渡方式传文件的服务器,需要把nginx装上、跑起来、开机能自启,后续还得能平滑重载配置。你会看到的不只是几条命令,而是每一步背后的判断依据——为什么选源码编译而不是搬RPM包、为什么openssl要用源码而不是系统自带的开发包、为什么编译通过了却启动失败。这些是文档里不会写、但在真实交付现场一定会遇到的东西。

我先把话说在前面:离线安装nginx有两条路,RPM包搬运和源码编译,两条路我都用过,各有各的适用边界,选错了后面的时间成本会翻好几倍。下面我会把两条路都讲透,然后重点展开源码编译这条更通用、更可控的路线。

1.1 离线安装真正的三个卡点

第一个卡点是依赖链的完整性。nginx的源码本身很干净,但它依赖PCRE做正则匹配(location里的正则、rewrite规则都靠它)、依赖zlib做gzip压缩、如果要用HTTPS还要依赖OpenSSL。更底层的问题是,编译这四样东西又需要gcc、make、glibc-devel、kernel-headers这一整套工具链。你在有网的机器上装这些只需要一句yum install -y gcc make pcre-devel zlib-devel openssl-devel,但在离线环境里,这句话背后的几十个rpm包得一个个凑齐。

第二个卡点是环境一致性。你的中转机(用来下载和打包的机器)和目标机的操作系统版本、glibc版本、CPU架构必须对得上。我在一个项目里吃过亏:中转机是CentOS 7.9,目标机是CentOS 7.6,搬过去的rpm包因为glibc版本依赖关系直接装不上,报了一堆Requires: libc.so.6(GLIBC_2.17)(64bit)之类的错。后来我养成了一个习惯,动手前先在两台机器上跑cat /etc/os-release、uname -r、uname -m、ldd --version,四项对齐了再往下走。

第三个卡点是运行期依赖。很多人编译安装完,/usr/local/nginx/sbin/nginx这个文件确实存在,执行却报error while loading shared libraries。这就是典型的运行期动态库路径没配好。编译期的头文件和运行期的so文件是两回事,前者由-devel包提供,后者由主包提供,路径搜索规则又由/etc/ld.so.conf决定。这三层关系理不清,就会陷入"明明装过了为什么还找不到"的死循环。

提示:如果你所在的环境对安全合规要求很高,选型阶段就把"是否允许引入临时编译工具链"这个问题问清楚。有些生产机不允许装gcc,那就只能走RPM搬运路线,甚至直接搬静态编译好的二进制。

1.2 先确认目标机器的"底子"

动手前花十分钟摸底,能省掉后面几个小时。我一般会依次确认这几项:操作系统发行版与版本号、内核版本、CPU架构(x86_64还是aarch64)、已安装的gcc版本(gcc -v)、是否已有nginx占用80端口(ss -lntp | grep :80)、是否有旧版本nginx残留(which nginx、rpm -qa | grep nginx)、以及SELinux和防火墙的状态。这几项每一项都对后续操作有直接影响。

拿SELinux举例,如果它是Enforcing状态,你即便把nginx跑起来了,浏览器访问也可能返回403或者502,因为SELinux策略不允许nginx进程访问某些目录。再拿旧版本残留举例,如果系统里已经通过包管理器装过一个nginx,/usr/sbin/nginx和新编译的/usr/local/nginx/sbin/nginx会打架,systemd里到底启动哪个取决于unit文件的路径。把这些前置信息搞清楚,比急着解压安装包重要得多。

2. 两条路线怎么选:搬运RPM包还是直接源码编译

这个选择题我每年都要做几次,判断标准其实很简单:看你手上这批需要部署的机器数量、操作系统版本是否统一、以及是否允许安装编译工具链。下面把两条路线的真实成本摊开说。

2.1 RPM路线:省事但绑定系统

RPM路线就是在有外网的机器上,把nginx及其全部依赖下载成rpm包,搬到目标机后用本地仓库的方式安装。好处非常明显:安装是二进制级别的,不涉及编译,速度快,systemctl start nginx一条命令就起来了,而且能共用系统的库,体积小。如果目标机有几十台且都是同一个系统版本,这条路线的效率碾压源码编译。

具体做法是在外网机器上准备好yum-utils,然后执行:

# 在外网中转机上执行 yum install -y yum-utils createrepo mkdir -p /tmp/nginx-rpms yumdownloader --resolve --destdir=/tmp/nginx-rpms nginx

--resolve这个参数是关键,它会自动把nginx依赖的所有包一并下载下来。如果发现有遗漏,改用repotrack更彻底,它会把整条依赖树全部拉下来:

repotrack -p /tmp/nginx-rpms nginx

包拉齐之后生成一份本地元数据,方便目标机用yum安装:

createrepo /tmp/nginx-rpms tar czf nginx-rpms.tar.gz -C /tmp nginx-rpms

到了目标机,解压后在/etc/yum.repos.d/下建一个指向本地目录的repo文件:

[local-nginx] name=Local Nginx Repo baseurl=file:///opt/nginx-rpms enabled=1 gpgcheck=0

然后yum clean all && yum makecache && yum install -y nginx就能装了。

这条路线的坑主要有三个:一是系统版本必须严格对齐,主版本号差一点都可能因为依赖版本不匹配失败;二是架构不能混,x86_64的包在aarch64机器上装不了;三是默认安装路径由发行版决定,配置文件在/etc/nginx/,二进制在/usr/sbin/,日志在/var/log/nginx/,和你源码编译时的布局完全不一样,运维脚本得跟着改。

2.2 源码编译路线:可控但依赖链条长

源码编译的优势在于"一切自己说了算"。安装路径、模块开关、依赖的openssl版本、运行用户,全部可控。特别适合这几种场景:目标机系统版本混杂、需要特定版本的OpenSSL(比如系统自带版本太老不支持TLS 1.3)、需要打开一些发行版默认没编进去的模块(比如stream四层代理、http_realip_module)、或者目标机架构比较特殊(ARM64、国产化平台的某些指令集)。

代价就是依赖链条特别长。下面是源码编译nginx需要的东西,我按"构建期"和"运行期"分开列出来,这个区分很重要:

依赖类型组件时机说明
构建工具gcc、make、binutils编译时没有gcc一切免谈,离线环境要单独准备
构建工具glibc-devel、kernel-headers编译时提供标准库头文件
功能库pcre 源码包编译+运行正则支持,可静态编入避免运行期依赖
功能库zlib 源码包编译+运行gzip压缩支持
功能库openssl 源码包编译+运行HTTPS支持,建议静态编入
运行环境nginx系统用户/组运行时worker进程降权运行

我的做法是把pcre、zlib、openssl都通过源码方式静态编入nginx,这样编出来的nginx二进制基本不依赖额外的so文件,搬到同类系统上直接能跑,省掉了运行期找库的麻烦。这算是源码编译路线里最值得花时间优化的一步。

2.3 我的实际判断标准

总结成一句话:机器多、系统统一、允许用包管理器,走RPM;机器少、系统杂、需要定制模块或特定SSL版本,走源码编译。还有一种情况是两者结合——先用RPM把gcc、make这些构建工具装到目标机(当然也得离线搬),然后再源码编译nginx。这个组合在很多只给一次摆渡机会的场景里特别实用。

3. 在中转机上把"弹药"备齐

这一节讲的是准备工作,很多人嫌麻烦跳过,结果在目标机上手忙脚乱。我的经验是,准备阶段多花一小时,现场能少熬三小时。准备工作分三件事:确定版本、下载源码、校验打包。

3.1 确定版本组合并核对依赖

版本选择上我不建议追最新。nginx官方稳定分支(stable)通常比主线分支(mainline)更适合生产环境,因为它经过了更多实际验证。OpenSSL选1.1.1系列或3.x系列都行,但要注意nginx版本对OpenSSL 3.x的支持情况——较老的nginx版本在编译时可能会报一些废弃API的警告。PCRE现在有PCRE1和PCRE2两个分支,nginx 1.21.5之后开始支持PCRE2,如果版本较新建议用PCRE2。

选定版本后,在中转机上把源码包下载齐:

mkdir -p /opt/nginx-src && cd /opt/nginx-src # 以下四个包建议版本固定,避免环境漂移 # nginx-1.24.0.tar.gz # pcre2-10.42.tar.gz # zlib-1.3.tar.gz # openssl-3.0.13.tar.gz sha256sum *.tar.gz

把sha256sum的输出记下来,搬到目标机后要再算一遍做校验。这个动作在文件摆渡场景里非常重要,传输过程中文件损坏导致编译报一堆莫名其妙的语法错误,排查起来非常折磨人。

3.2 构建工具链的离线准备

如果目标机没有gcc,那还得准备一套编译工具链。在和中转机系统版本完全一致的环境里,把下面这些包拉下来:

mkdir -p /tmp/build-tools yumdownloader --resolve --destdir=/tmp/build-tools \ gcc gcc-c++ make binutils glibc-devel kernel-headers \ glibc-headers libmpc mpfr gmp cpp

这一步容易被忽略的是kernel-headers和glibc-headers,没有它们gcc能装但编不了东西,会报stdio.h: No such file or directory这种看似低级但很致命的错误。另外像libmpc、mpfr、gmp这些是gcc自己的依赖,--resolve一般会带上,但保险起见我习惯显式列出来。

3.3 打包、校验与搬运

把所有东西按类别打包,别一股脑塞进一个tar里。我的目录习惯是这样的:

# 最终打包结构 # nginx-offline/ # ├── src/ 源码包(nginx/pcre2/zlib/openssl) # ├── tools/ 构建工具rpm包 # ├── install.sh 一键安装脚本 # └── SHA256SUMS 校验清单

打包命令:

cd /opt tar czf nginx-offline-$(date +%Y%m%d).tar.gz nginx-offline sha256sum nginx-offline-*.tar.gz > nginx-offline-*.tar.gz.sha256

搬运过程中如果遇到文件名乱码,绝大多数情况是打包机的locale和目标机不一致导致的。规避方法是打包和传输时统一用LANG=C,或者干脆所有文件名都用英文。解压时用tar xzf 包名 -C 目标目录指定绝对路径,别依赖当前工作目录,能避免一大半路径相关的怪问题。

注意:U盘、光盘这类介质在不同文件系统上对权限位、文件名长度的处理不一样。如果安装脚本里带了可执行位,拷过去之后可能变成644,记得chmod +x补回来。

4. 源码编译安装的完整落地过程

准备工作到位之后,目标机上的操作其实是相对机械的。但每一步都有它的道理,我会在命令旁边说清为什么这么写。

4.1 编译环境自检与目录规划

先把工具链装上(假设已经通过本地repo或者rpm -ivh装好了gcc),然后自检:

gcc --version make --version ldd --version | head -1

三条命令都能正常输出,说明构建环境就绪。接着创建运行用户和目录。nginx以root启动master进程、以非root身份运行worker进程,这是它的标准安全模型,所以必须有专门的运行用户:

groupadd -r nginx useradd -r -g nginx -s /sbin/nologin -M nginx mkdir -p /usr/local/nginx/conf /usr/local/nginx/logs mkdir -p /var/log/nginx chown -R nginx:nginx /var/log/nginx

-r表示创建系统用户(UID小于1000),-M表示不创建家目录,-s /sbin/nologin表示不允许登录。这三件套配齐,worker进程就能以一个受限身份跑起来,即使将来某个目录被攻破,攻击面也被压缩了。

4.2 configure参数的逐项说明

解压四个源码包,然后进入nginx目录执行./configure。这里我要强调一个顺序问题:PCRE和zlib如果通过--with-pcre=和--with-zlib=指定源码目录,需要先在这两个目录里执行过一次./configure,否则nginx编译阶段会因为找不到Makefile而失败。OpenSSL则不需要,nginx会自己调用它的Configure。

tar xzf pcre2-10.42.tar.gz && cd pcre2-10.42 && ./configure && cd .. tar xzf zlib-1.3.tar.gz tar xzf openssl-3.0.13.tar.gz tar xzf nginx-1.24.0.tar.gz && cd nginx-1.24.0 ./configure \ --prefix=/usr/local/nginx \ --sbin-path=/usr/local/nginx/sbin/nginx \ --conf-path=/usr/local/nginx/conf/nginx.conf \ --pid-path=/run/nginx.pid \ --lock-path=/run/nginx.lock \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --user=nginx \ --group=nginx \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-http_realip_module \ --with-stream \ --with-pcre=../pcre2-10.42 \ --with-zlib=../zlib-1.3 \ --with-openssl=../openssl-3.0.13 \ --with-openssl-opt="no-tests -fPIC"

几个参数值得单独说。--with-http_stub_status_module打开状态页,运维监控nginx当前连接数全靠它,几乎必开。--with-http_realip_module在nginx前面还有一层负载均衡时用来取真实客户端IP,不装这个模块拿到的全是上游的地址。--with-stream是四层代理模块,做TCP/UDP转发必须用它,很多发行版默认没编。--with-openssl-opt="no-tests -fPIC"里的no-tests能显著缩短OpenSSL的编译时间,-fPIC是为了生成位置无关代码,静态链接时更稳。

如果你需要更小的二进制体积或者更快的启动速度,还可以加上--with-cc-opt='-O2'和--with-ld-opt='-Wl,-rpath,/usr/local/nginx/lib',后者把运行期库搜索路径直接烧进二进制,能省掉配ld.so.conf的步骤。

4.3 编译、安装与首次启动

configure通过之后会打印一份摘要,一定要看一眼,确认PCRE、zlib、OpenSSL三项都显示为找到的状态,并且HTTPS模块已经启用。确认无误再编译:

# 用CPU核数并行编译,能快不少;内存小的机器把-j调小 make -j$(nproc) make install

make install之后,二进制就在/usr/local/nginx/sbin/nginx了。先做一次语法检查再启动:

/usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx ss -lntp | grep :80 curl -I http://127.0.0.1

-t是配置文件语法测试,这个习惯一定要养成。生产环境里改完配置直接reload,如果配置有语法错误,nginx会拒绝加载但老进程还在跑,表面上没问题,等到下次重启才发现配置是坏的。每次改配置先-t再reload,是从业者的基本纪律。

5. 那些报错背后的真实原因:排查链路复盘

这一节是我认为最有价值的部分。编译和启动阶段会遇到的报错就那么几类,但每一类的根因和修复方式差异很大。我把当时排查的完整思路还原出来,你可以照着这个顺序定位。

5.1 启动报 error while loading shared libraries

报错长这样:error while loading shared libraries: libpcre.so.1: cannot open shared object file。第一次遇到会以为是pcre没装,其实很可能装在了/usr/local/lib而系统不认识这个路径。排查顺序是这样的:先用ldd /usr/local/nginx/sbin/nginx | grep "not found"看看到底缺哪些库;再用find / -name "libpcre*" 2>/dev/null找到库的实际位置;最后把库所在目录加到搜索路径里:

echo "/usr/local/lib" > /etc/ld.so.conf.d/nginx-local.conf ldconfig ldd /usr/local/nginx/sbin/nginx | grep "not found"

ldconfig会重建动态链接库缓存,重新执行后ldd应该不再有not found。这个坑的根本原因在于,Linux查找动态库的路径里默认不包含/usr/local/lib,而这个目录恰恰是源码编译最常输出的位置。

5.2 configure阶段找不到PCRE或OpenSSL

报错通常是the HTTP rewrite module requires the PCRE library。这时候要分两种情况判断:如果你用的是系统自带的pcre-devel,那大概率是没装或者装在非标准路径;如果你指定了--with-pcre=../pcre2-10.42,那就回到上一节说的问题——这个目录里必须先有./configure生成过的Makefile。还有一种隐蔽情况是PCRE1和PCRE2的API不兼容,老版本nginx配PCRE2会编译失败,这时候要么降PCRE版本,要么升nginx版本。

OpenSSL的问题更有意思。报错可能是SSL modules require the OpenSSL library,但实际上你已经指定了源码路径。常见原因是openssl源码目录里残留了上一次失败编译的产物,状态不干净。我的处理方法很粗暴但有效:删掉openssl目录重新解压,确保是全新的。另外,如果系统里同时存在多个openssl版本,configure脚本可能会优先用系统的,用--with-openssl=显式指定可以强制覆盖。

5.3 权限、SELinux与防火墙三连

nginx起来了,curl 127.0.0.1也通了,但从别的机器访问就是不行。按这个顺序查:第一,防火墙有没有放行80端口,firewall-cmd --list-ports看一眼,没有就用firewall-cmd --permanent --add-port=80/tcp && firewall-cmd --reload加上。第二,SELinux是不是Enforcing,getenforce确认,如果是,可以用ausearch -m avc -ts recent看最近的拒绝日志,也可以用setsebool -P httpd_can_network_connect 1放开网络访问权限。第三,nginx是否只监听了127.0.0.1,检查配置里listen指令有没有写死本地回环。

这三项排查下来,九成的"访问不通"都能定位。我特别提醒一句,不要图省事直接setenforce 0把SELinux关掉,这在很多生产环境是不允许的操作,而且会掩盖真正的问题。用semanage fcontext给nginx需要访问的目录打上正确标签,才是正规做法。

5.4 中文路径解压乱码与文件权限错乱

前面提过文件名乱码。如果已经乱了,可以用convmv -f gbk -t utf-8 -r --notest 目录名批量转换,但前提是系统里有convmv这个工具,离线环境又得单独搬。所以最省事的方案还是从一开始就用纯英文路径和文件名。

权限错乱的表现是编译时报Permission denied或者cannot execute binary file。根源往往是挂载的U盘或光盘默认带noexec选项,这时候需要把文件先拷到本地磁盘(比如/tmp)再操作,别直接在挂载点上跑脚本。

6. 让nginx稳定住下来:systemd托管与开机自启

手动./sbin/nginx启动的进程,机器一重启就没了,这不是生产环境该有的样子。用systemd托管是标准做法,但unit文件里有几个细节不注意会踩坑。

6.1 手写unit文件的关键细节

在/etc/systemd/system/nginx.service写入:

[Unit] Description=nginx - high performance web server Documentation=https://nginx.org/en/docs/ After=network-online.target remote-fs.target nss-lookup.target Wants=network-online.target [Service] Type=forking PIDFile=/run/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=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID PrivateTmp=true Restart=on-failure RestartSec=3 LimitNOFILE=65535 [Install] WantedBy=multi-user.target

逐项说明:Type=forking是因为nginx本身是守护进程模式,master进程会fork后返回,这个类型必须配对。PIDFile必须和--pid-path编译参数保持一致,否则systemd找不到进程,stop和reload都会失效——这是最典型的坑。ExecStartPre里放-t是加了一层保险,配置错了根本不会启动,而不是启动后带病运行。LimitNOFILE=65535把文件描述符上限拉高,高并发场景下默认的1024根本不够用,连接数稍多就会报too many open files。

最后三件套:

systemctl daemon-reload systemctl enable nginx systemctl start nginx systemctl status nginx

daemon-reload不能省,改了unit文件不执行这一句,systemd用的还是旧定义。

6.2 日志切割与平滑重载

nginx自己不会切割日志,access.log会一直涨。我用logrotate来管:

/var/log/nginx/*.log { daily missingok rotate 30 compress delaycompress notifempty create 0640 nginx nginx sharedscripts postrotate [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid) endscript }

关键是postrotate里发的USR1信号,它让nginx重新打开日志文件,从而切换到新文件继续写。如果只做了文件重命名而没有发信号,nginx会继续往已经被移走的旧文件句柄里写,磁盘空间不会释放,这是个非常隐蔽的坑。

至于配置重载,systemctl reload nginx对应的是HUP信号,nginx会用新配置启动新worker、优雅关闭老worker,整个过程不断连接。唯一要记住的是:重载前一定要nginx -t,重载不会校验配置合法性,坏的配置会直接导致reload失败。

7. 上线前的自检清单与长期维护心得

交付前我有一套固定动作,逐项过一遍基本不会漏。这套清单用了好几年,救过我好几次。

检查项命令期望结果
配置文件语法/usr/local/nginx/sbin/nginx -tsyntax is ok / test is successful
运行期依赖ldd /usr/local/nginx/sbin/nginx | grep "not found"无输出
编译模块/usr/local/nginx/sbin/nginx -V含所需模块名
监听端口ss -lntp | grep nginx80/443处于LISTEN
开机自启systemctl is-enabled nginxenabled
本机访问curl -I http://127.0.0.1HTTP/1.1 200
远程访问从另一台机器curl -I http://目标IP同上
进程用户ps -ef | grep nginxworker进程用户为nginx

7.1 备份与升级的实操建议

源码编译安装的nginx升级比RPM麻烦一点,但也不算复杂。我的做法是先装到新前缀目录(比如/usr/local/nginx-new),验证通过后再改软链或者替换二进制,然后HUP重载。这样出问题可以秒级回滚。千万别直接覆盖正在运行的二进制文件,虽然Linux允许这么做(inode还在),但一旦重启就可能起不来。

配置文件也要备份。我习惯把/usr/local/nginx/conf整个目录纳入版本管理,每次改动前先打一个时间戳备份,例如cp -a conf conf.bak.$(date +%Y%m%d%H%M)。这套动作花不了几秒钟,但在配置改崩的时候是唯一的救命稻草。

7.2 关于离线安装这件事的一点体会

我最后想说的是,离线安装nginx这件事,真正考验的不是你对nginx的熟悉程度,而是你对Linux整个构建体系的掌控力——头文件和库文件的区别、编译期和运行期的区别、包管理器和源码安装的边界、systemd和传统init脚本的差异。这些东西在有网环境里被yum install一句话掩盖了,只有断网的时候才会全部暴露出来。

我的建议是,即便平时都在有网环境工作,也找个时间在虚拟机上完整走一遍源码编译流程,把每一步的报错都踩一遍。等到真的面对一台断网的生产机时,你会发现这套经验的价值远超你预期的想象。另外一个实用小技巧:把整个离线安装过程写成一个带错误检查的shell脚本,每步执行后判断返回码,失败就打印出问题的那一步和对应的排查建议。这套脚本在批量交付场景下,能把单机部署时间从四十分钟压缩到五分钟以内。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询