
银河麒麟V10 SP3内网服务器没有外网权限还要装Nginx 1.21.5——这大概是这两年国产化项目里我碰到最多的组合需求之一。这个版本号不是随手填的1.21.x属于当时主线分支里的稳定版本HTTP/3实验特性、ssl_conf_command这些新能力都在里面很多项目交付文档里直接点名要这个版本。问题在于目标机器连yum源都无法访问唯一能指望的就是一套rpm依赖包外加一个tar.gz源码包。这篇文章把我在银河麒麟V10 SP3上离线安装Nginx 1.21.5的完整过程整理出来包括我实际用到的11个rpm包下载清单、每条命令的用途、踩过的坑以及最后一份可以直接抄的systemd管理配置。不管你是日常做服务器运维还是给信创项目做技术预研照着这套流程走一遍基本都能装成。整个过程不复杂但坑都在细节里提前讲清楚能省你半天时间。1. 背景与方案选型为什么非得离线编译1.1 这个需求到底有多常见先说背景。银河麒麟V10是国产服务器操作系统里占有率很高的一支V10 SP3对应的是比较新的服务端版本兼容CentOS 7体系的二进制接口。内网环境部署Web服务时Nginx几乎是绕不开的组件要么做前端静态资源托管要么做后端接口服务的反向代理要么就是给多个Web项目做入口的负载均衡。我这次接手的目标机是一台数据采集前置服务器部署在内网独立网段除了业务数据端口外对外访问全部屏蔽yum源连不上、互联网更别想。业务方给出的需求很明确前端页面需要跑在一个独立的Web服务上后端有多套API服务需要通过域名区分访问还得支持HTTPS所以Nginx必须带上SSL模块编译。因为现场条件限制我选择了“联网机器拉取rpm包 离线机源码编译”的组合方案。1.2 为什么不直接用rpm装Nginx本体很多新接触Linux运维的同学会问既然你都准备了rpm包为什么不直接找个nginx-1.21.5的rpm包装上去理论上可行但实际会遇到几个麻烦。首先Nginx官方rpm仓库提供的版本和发行版自带源的版本往往滞后想精确锁定1.21.5这个版本需要自己去归档路径找对应RHEL/CentOS 7的rpm难度不大但费时。其次rpm方式安装Nginx会有更严格的依赖约束比如对openssl、pcre、zlib这些基础库的具体版本有要求离线环境下凑齐整套依赖树工作量比源码编译只多不少。第三业务方要的是一台内网服务器上的定制化部署编译安装可以把很多不需要的模块砍掉编译参数完全可控后续排查问题也更直接。所以我最终定的方案是rpm包只用来解决编译依赖Nginx本体用源码编译安装。这样依赖范围可控11个rpm包足够覆盖不会出现拉几十个包还缺依赖的情况。2. 动手前的准备版本确认与工具链检查2.1 先确认你的系统是什么版本不同版本的银河麒麟包管理方式和兼容层会有细微差别动手前一定要先确认。我习惯一条命令同时看发行版信息和内核版本cat /etc/os-release uname -a在银河麒麟V10 SP3服务器版上/etc/os-release会显示系统名称、版本号uname -a能看到内核版本。SP3一般对应4.19内核系列整体和RHEL/CentOS 7的软件生态是对得上的。这里还要顺手确认一下架构x86_64还是ARM64决定你去下载哪个版本的依赖包。命令是arch我这次是x86_64环境所以后面所有包都是x86_64架构。如果你的机器是飞腾、鲲鹏这类ARM架构下载rpm包时要把架构后缀换成aarch64其余流程基本一致。2.2 检查系统里有没有残留的nginx这是个很容易被忽略的步骤。有些银河麒麟系统镜像在交付时可能预装了nginx或者之前部署其他业务时塞过一份。如果系统里已经存在旧版nginx直接编译新版本可能会造成端口冲突、命令覆盖混乱。检查方法很简单rpm -qa | grep nginx which nginx如果结果为空说明是干净环境可以放心继续。如果有输出建议先把旧包卸载干净再编译新版本除非你确实需要在同一台机器上共存多套Nginx。2.3 准备好一台联网的“摆渡”机器因为目标机上不了网准备依赖包必须在一台能联网的机器上完成。这台机器建议选择同样基于CentOS/RHEL 7体系的系统比如另一台银河麒麟V10、CentOS 7.x或者Rocky Linux 8早期版本。为什么不推荐CentOS 8/9或Rocky 9因为el9仓库里默认的openssl、pcre版本太高拉下来的包在银河麒麟V10 SP3上可能因为glibc版本不兼容而装不上徒增烦恼。联网机器上需要能正常使用yum命令这是最核心的工具。后面拉包和整理清单都靠它。3. 11个rpm包完整清单与依赖关系解读3.1 核心11个包的清单表这是我这次离线安装实际用到的11个rpm包全部来自CentOS 7.9的base/os和updates源架构x86_64。你如果在其他麒麟版本上装版本号可能略有差异但包的名称和作用是对应的包名版本示例作用备注gcc4.8.5-44.el7C语言编译器编译Nginx主体必需gcc-c4.8.5-44.el7C编译器部分辅助代码需要make3.82-24.el7构建工具configure后执行make必需pcre8.32-17.el7正则表达式库rewrite模块依赖pcre-devel8.32-17.el7PCRE开发头文件configure检查的关键zlib1.2.7-19.el7压缩库gzip模块依赖zlib-devel1.2.7-19.el7zlib开发头文件configure检查的关键openssl1.0.2k-21.el7SSL/TLS库HTTPS支持必需openssl-devel1.0.2k-21.el7OpenSSL开发头文件configure检查的关键glibc-devel2.17-317.el7C标准库开发文件gcc编译时的基础依赖libstdc-devel4.8.5-44.el7C标准库开发文件gcc-c的依赖这里有个细节需要注意pcre、zlib、openssl这三个名字很熟悉但编译Nginx时真正起作用的是带-devel后缀的开发包。devel包里提供的是头文件.h比如pcre.h、zlib.h、openssl/ssl.hconfigure脚本在检查依赖时找的就是这些头文件。很多人只装了pcre本体没装pcre-develconfigure阶段报错找不到PCRE库其实就是这个原因。3.2 为什么是这11个而不是更多细心的同学会发现gcc真正跑起来还需要cpp、binutils、mpfr、libmpc这些间接依赖。为什么我这里只列了11个原因是银河麒麟V10 SP3系统镜像本身已经自带了一部分基础运行库比如glibc本体、libstdc本体、kernel头文件等通常都在系统里。我在联网机器上用repoquery查过依赖树实际缺的、且不依赖其他缺失包的就是这11个。但这里我强烈建议你不要机械地照抄清单而是在自己的环境下用依赖工具再核对一遍因为不同版本麒麟系统预装情况不一样。如果不想手动一个个核对最稳妥的方式是利用yum的downloadonly功能一次性把依赖全部拉下来这个放到下一小节详细说。3.3 推荐用downloadonly一键拉全依赖这是我在日常工作中最常用的离线包准备方法比手动逐个下载rpm包可靠得多。在联网机器上执行mkdir -p /root/nginx_rpms yum install --downloadonly --downloaddir/root/nginx_rpms \ gcc gcc-c make \ pcre pcre-devel \ zlib zlib-devel \ openssl openssl-devel这条命令的语义是只下载不安装把指定的软件包以及它们所有缺的依赖全部放到/root/nginx_rpms目录下。downloadonly需要yum-plugin-downloadonly插件CentOS 7和多数麒麟版本默认已集成如果你的机器提示找不到该参数先执行yum install -y yum-plugin-downloadonly装一下插件。执行完以后这个目录里的rpm文件数量可能比我上面列出的11个多因为还包括了cpp、binutils等间接依赖多就多吧不影响使用。把这个目录整个拷到U盘再传到目标机上用下面这条命令一键安装即可cd /root/nginx_rpms rpm -Uvh *.rpm --nodeps --force注意--nodeps --force这两个参数要用得有分寸。从头安装依赖库时可以用但如果目标机上已经有新版openssl再强制装旧版可能会破坏系统依赖所以刚上机时先正常执行rpm -Uvh *.rpm报依赖冲突了再考虑加--force。4. 离线安装实操全流程4.1 安装rpm依赖包把U盘里的rpm包目录全部拷到目标机器后先进入目录看一眼文件是否完整ls -lh /root/nginx_rpms/*.rpm确认没问题后执行安装cd /root/nginx_rpms rpm -Uvh *.rpm这时候会看到一串Preparing... ################################# [100%]的回显以及所有rpm包的安装过程。如果中途报某个包已经存在比如系统自带了一个更高版本的openssl就用rpm -qa | grep openssl查一下具体版本只要不影响后续编译跳过已存在的包即可不必强行覆盖。安装完成后建议验证一下编译工具是否可用which gcc which make gcc --version只要gcc --version能正常输出版本信息说明工具链没问题可以继续。4.2 准备Nginx源码包Nginx 1.21.5的源码包我是在一台能联网的机器上提前下载的文件名是nginx-1.21.5.tar.gz大小约1MB出头。把它同样放到U盘传到目标机的/usr/local/src目录下。在解压之前先创建一个用于运行Nginx的系统用户这是生产环境的标准做法避免用root直接跑Nginx worker进程id nginx || useradd -r -s /sbin/nologin nginx-r参数表示创建系统用户-s /sbin/nologin禁止该用户登录shell-t可省略。这样即使通过Nginx漏洞拿到shell执行权限也无法直接登录系统安全等级会高一些。接着解压源码包cd /usr/local/src tar -xzf nginx-1.21.5.tar.gz cd nginx-1.21.54.3 configure与编译参数选择进入源码目录后先不急着编译仔细配置编译选项。这一步是整个离线安装的核心模块选错后面补很麻烦。我这次选择的configure参数如下兼顾了静态资源服务和反向代理场景./configure \ --prefix/usr/local/nginx-1.21.5 \ --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-stream_ssl_module \ --with-pcre \ --with-zlib../zlib-1.2.7这里解释几个关键参数的用途。--prefix指定安装目录我习惯把版本号放进目录名里方便以后做版本切换--with-http_ssl_module必须加否则没法配置HTTPS站点--with-http_v2_module提供HTTP/2支持同类配置下对性能提升明显--with-stream是四层TCP/UDP代理模块做端口转发时会用到--with-http_realip_module解决后端服务获取真实客户端IP的问题反向代理场景非常刚需。需要特别提醒的是如果你是手动下载源码包而不是通过rpm安装依赖可能会出现--with-zlib指向的目录不存在的情况。这里更好的做法是不指定--with-zlib让configure自动去系统目录下找前提是第3节里的zlib-devel已经装好。我个人实际用的configure命令没有--with-zlib系统检测到/usr/include/zlib.h后自动启用。configure执行后屏幕会滚动刷出检测结果最后几行会显示Configuration summary。看到checking for PCRE library ... found、checking for zlib library ... found、checking for OpenSSL library ... found这些字样说明依赖全部就绪。如果中途报错先回到第3节核对devel包是否有缺席。configure通过后开始编译和安装make -j4 make install-j4表示用4个线程并行编译如果你的机器CPU核数多可以改成-j8或-j$(nproc)能明显加快编译速度。整个编译过程大概两三分钟结束后进入安装阶段。ls /usr/local/nginx-1.21.5能看到conf、sbin、logs等目录说明安装成功。4.4 配置环境软链接与systemd服务编译安装的Nginx可执行文件路径比较深如果每次敲命令都要输全路径很麻烦而且后续配置systemd、写脚本都要引用一个稳定的路径。我的做法是做一个软链接ln -s /usr/local/nginx-1.21.5 /usr/local/nginx ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx这样nginx命令全局可用配置路径也可以直接用/usr/local/nginx来引用升级版本时只要重新替换软链接目标即可。为了让Nginx能以系统服务的方式跟随开机自启需要手动创建一个systemd服务文件。源码编译的Nginx默认不带service文件这是编译安装和rpm安装最大的区别之一。创建/usr/lib/systemd/system/nginx.servicevim /usr/lib/systemd/system/nginx.service写入以下内容并保存[Unit] Descriptionnginx - high performance web server Documentationhttp://nginx.org/en/docs/ Afternetwork.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/usr/local/nginx/sbin/nginx -s quit PrivateTmptrue [Install] WantedBymulti-user.targetTypeforking表示Nginx主进程会fork出worker子进程systemd需要知道这个特性才能正确跟踪服务状态ExecStartPre里的-t是测试配置参数如果配置有语法错误启动会直接失败方便提前发现问题。配置完成后执行systemctl daemon-reload systemctl enable nginx systemctl start nginx systemctl status nginx看到Active: active (running)就说明服务正常起来了并且开机自启已经生效。5. Nginx配置与典型场景验证5.1 一套够用的nginx.confNginx装好后配置文件在/usr/local/nginx/conf/nginx.conf。我这次业务场景包含三部分一个前端静态站点、一个后端API反向代理、一个TCP端口转发。下面这份配置是精简后的核心结构可以直接套用worker_processes 4; pid /usr/local/nginx/logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; 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 /usr/local/nginx/logs/access.log main; server { listen 80; server_name frontend.example.com; location / { root /data/www/frontend; index index.html; } } server { listen 81; server_name api.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } upstream backend_servers { server 192.168.1.101:8080; server 192.168.1.102:8080; } server { listen 82; server_name balance.example.com; location / { proxy_pass http://backend_servers; } } } stream { upstream tcp_backend { server 192.168.1.200:3306; } server { listen 13306; proxy_pass tcp_backend; } }这份配置里能看到几个Nginx常用的核心能力。第一个server是静态站点直接把前端构建产物放到/data/www/frontend下就能跑第二个server是反向代理把api.example.com的请求转发给本机8080端口第三个server是负载均衡通过upstream定义了一组后端服务器Nginx会自动做轮询分发最后的stream块是四层代理用于把本机13306端口转发到内网数据库的3306端口这是--with-stream模块在起作用。日志路径我统一放到了/usr/local/nginx/logs/下access.log记录访问日志error.log记录错误日志。编译安装的Nginx默认日志路径就在安装目录的logs子目录里这一点要和rpm安装方式区分开rpm安装的默认日志路径通常是/var/log/nginx/很多从rpm转编译的同学就在这里找过半天。5.2 配置校验与启动配置文件改完以后最重要的是语法校验。用Nginx自带的测试命令nginx -t如果所有server块都配置正确会输出nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful。如果哪里写错了会直接提示在第几行有问题按提示修改即可。配置生效后用curl做一次简单验证curl -I http://127.0.0.1/ curl -I http://127.0.0.1:81/第一个命令验证前端静态站点返回200 OK说明页面正常访问第二个验证反向代理接口。如果返回502或504说明后端服务没有启动或者代理配置的端口不对可以结合error.log一起排查。5.3 防火墙端口放行内网服务器一般开着firewalld防火墙新安装的Nginx监听80端口如果不放行外部机器无法访问。操作命令firewall-cmd --permanent --add-port80/tcp firewall-cmd --permanent --add-port81/tcp firewall-cmd --permanent --add-port82/tcp firewall-cmd --reload如果业务环境用的是iptables等价的操作是iptables -I INPUT -p tcp --dport 80 -j ACCEPT service iptables save很多人在离线环境里装好了Nginx本地curl正常但其他机器访问不了大概率就是防火墙这步漏了。建议装完立刻放行省得后面再排查一轮。6. 常见问题与排查技巧实录6.1 “没找到rpm命令”到底怎么回事有朋友在群里问过“银河麒麟系统上输rpm提示找不到命令”出现这个提示通常有两种情况。一种是当前shell环境的PATH变量里没包含/usr/bin比如用了某些精简bash环境此时直接输入全路径/usr/bin/rpm -qa就能用。另一种是系统确实没安装rpm相关组件这很少见但并非不可能可以用yum install -y rpm补装。补充一句银河麒麟V10是rpm系的发行版不是Debian系的所以不要试图用apt-get install nginx来装软件。麒麟V10 SP3默认带有yum命令如果你在离线环境下配好了本地源完全可以用yum进行软件安装这也是很多运维同学处理离线依赖的首选方式。6.2 configure阶段三大报错这一块是我踩坑最多的整理成速查表方便对照报错信息原因解决办法checking for C compiler ... not foundgcc没装或不在PATHrpm安装gccwhich gcc确认the HTTP rewrite module requires the PCRE library缺pcre-devel安装pcre-devel包the HTTP gzip module requires the zlib library缺zlib-devel安装zlib-devel包the HTTP SSL module requires the OpenSSL library缺openssl-devel安装openssl-devel包这三种报错里第一种多半是第4.1步没执行成功或者rpm安装时有依赖冲突被强行跳过了后三种的核心都指向同一个问题本体库装了但devel开发包没装。configure脚本是通过查找头文件来判断库是否存在的纯库文件满足不了它。6.3 编译通过但启动失败启动失败最经典的原因就是端口被占用。执行systemctl start nginx后如果提示失败先看错误日志tail -50 /usr/local/nginx/logs/error.log如果看到bind() to 0.0.0.0:80 failed (98: Address already in use)说明80端口已有进程占用。用netstat -tlnp | grep :80或ss -tlnp | grep :80找到占用进程要么停掉它要么修改Nginx监听端口。还有一种情况是配置里pid路径写错了systemd的PIDFile指向的路径和nginx.conf里pid指令不一致启动后systemd以为服务没起来。解决办法是确保两处路径完全一致。6.4 重启后Nginx没自启如果重启时自启失败先看服务是否正常enablesystemctl is-enabled nginx如果输出disabled执行systemctl enable nginx。如果输出linked但启动失败大概率是nginx.service文件里ExecStart的路径写错了用systemctl cat nginx查看实际加载的配置再逐个核对路径。另一个隐蔽问题如果/usr/local所在分区在启动早期还没有挂载systemd可能在挂载前就尝试启动nginx导致路径找不到。这种情况要在service文件里增加Afterlocal-fs.target并考虑加RequiresMountsFor/usr/local确保文件系统就绪后再启动服务。我的实际部署中暂时没遇到这个问题但知道这个解法可以省很多排查时间。6.5 日志文件找不到了编译安装Nginx的日志路径非常依赖configure时的--prefix。如果你把prefix指定为/usr/local/nginx-1.21.5那么默认access.log和error.log都会在/usr/local/nginx-1.21.5/logs/下。我通过软链接把/usr/local/nginx指到了这个目录所以很多配置里写的是/usr/local/nginx/logs/两者指向同一个物理位置。如果你在日志文件里看到open() /usr/local/nginx/logs/access.log failed (2: No such file or directory)别慌不是权限问题而是logs目录不存在或路径没对齐。用mkdir -p /usr/local/nginx/logs先建目录或者检查nginx.conf里pid、access_log、error_log的路径是否和实际目录一致。6.6 修改配置后不生效改完nginx.conf以后一定要执行nginx -t先做语法检查再执行systemctl reload nginx或者nginx -s reload让配置生效。reload是优雅重载不会中断现有连接比restart更平滑。如果你改了listen端口不生效注意可能不是配置没加载而是防火墙没放行新端口这个和第5.3节提到的坑是配套出现的。另外部署多个Web项目时经常有人在一个server块里堆十几个location结果nginx -t报conflicting server name这是server_name重复导致的。排查方法是搜索所有server块里的server_name确保不重复。如果确实要在一个IP上用域名区分多个项目用不同server_name来区分而不是在同一个server里无限堆location。7. 收尾一套稳定的离线包仓库是长期资产这次离线安装做完以后我把用到的11个rpm包、nginx-1.21.5.tar.gz、service文件模板、nginx.conf示例都归档到了一个目录里刻了一张光盘存档同时在公司内部的文档平台留了一份。原因很简单这种内网机器不是装一台就完事了后续大概率还会来第二台、第三台。与其每次都重新找包、重新试编译参数不如第一次就把整套环境沉淀成标准化资产。后面再遇到同架构、同版本的机器直接U盘一插十分钟就能完成部署。我个人在实际操作中的体会是离线安装软件最耗时间的其实不是编译本身而是依赖关系的清理和版本匹配。如果你是在干净环境下从零开始用第3.3节的yum downloadonly方式拉全依赖是最省心的如果你在已有大量软件的系统上操作先确认已装包版本再决定是否覆盖更新避免为了装Nginx把系统的openssl版本搞乱了。装完以后一定要做一次nginx -t、一次curl -I、一次systemctl restart nginx的完整验证确保配置、服务、端口三个维度都正常。最后再说一个细节如果你后续要升级Nginx版本比如从1.21.5升到1.22.x只需要重新下载新源码包编译时保持--prefix不变make install会覆盖旧版可执行文件配置文件和数据目录都不会丢失。升级前记得备份一下/usr/local/nginx/conf/nginx.conf这是你所有配置的核心资产。这套方法在银河麒麟V10 SP3上验证过在多数兼容CentOS 7体系的系统上同样适用。