ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Nginx安装全攻略:源码编译、包管理器与Docker部署对比

Nginx安装全攻略:源码编译、包管理器与Docker部署对比 干运维这一行的谁没被Nginx折磨过两回但说实话折腾多了你会发现Nginx的安装压根不是技术问题而是选择问题。同一个Nginx在不同机器、不同场景下安装方式差了十万八千里后续的维护体验也完全不一样。今天这篇就专门聊聊Nginx三种主流的安装方式源码编译安装、系统包管理器安装、Docker容器化部署。我会把这三种方式从原理到实操、从优缺点到坑点全部掰开揉碎讲清楚顺便把我这些年踩过的坑一并交代了。不管你是刚接触Nginx的新手还是准备在公司服务器上部署的老手这篇都有参考价值。1. 三种安装方式的选择逻辑先想清楚再动手很多人一上来就敲命令装完才发现模块不够、路径不对、没法平滑升级然后被迫重装。Nginx安装这事儿十有八九的问题都出在动手前没想明白。1.1 为什么安装方式如此关键Nginx最强大的地方在于它的模块化设计但这也恰恰是安装时最需要动脑子的地方。编译安装时多一个--with参数、少一个模块到后面配置SSL证书、做TCP代理、加状态监控时差别立刻就体现出来了。举个最典型的例子很多人用包管理器装完Nginx配置HTTPS时发现缺少http_ssl_module只能干瞪眼。这时候无非两条路要么找第三方源装带全模块的版本要么重新编译。无论哪条都比一开始就规划好要费事得多。再说升级问题。Nginx的漏洞和Bug修复频率不算低如果你用的是编译安装升级时要重新走一遍编译流程用包管理器则一条命令搞定用Docker就更简单换个镜像重新起容器就行。三种方式在运维层面的成本差距越到后期越明显。1.2 不同场景的选型建议根据我这几年在测试环境和生产环境的部署经验选型逻辑大致是这么个思路源码编译安装适合需要定制模块、追求极致性能、或者内网环境没有现成包的情况。比如你要加http_v2_module、stream模块或者某些特殊第三方模块编译安装是唯一靠谱的路子。缺点是对新手不太友好编译参数错了、依赖缺了排查起来有点头疼。系统包管理器安装适合大多数常规场景尤其是刚上手Nginx的人。CentOS的yum、Ubuntu的apt都能直接装装完就是标准目录结构systemctl管理服务配置文件位置固定网上教程最多出了问题也好搜。缺点也很明显——官方源里的版本经常偏老模块不全。Docker容器化部署适合微服务架构、多环境快速复用、以及不想污染宿主机环境的场景。一条docker run命令就能起一个Nginx配置挂在宿主机上升级就是换镜像的事儿。但对网络模式、端口映射、挂载目录不熟悉的话刚开始也容易绕晕。我个人的建议很简单新手先用包管理器把Nginx跑起来理解它的工作方式和配置文件结构等需要个性化定制了再尝试编译安装等理解了Nginx的机制再上手Docker部署。循序渐进比一步到位稳得多。2. 源码编译安装自由度和性能的极致追求源码编译是三种方式里最“原教旨主义”的一种也是最能体现Nginx本来面目的安装方式。虽然步骤繁琐但装完之后你对Nginx的理解会上升一个层次。2.1 编译前的准备工作先说环境和依赖。不同发行版的依赖包名字不太一样但核心就那几个GCC编译器、PCRE库正则表达式支持、zlib库gzip压缩支持、OpenSSL库HTTPS支持。# CentOS / RHEL 系列 yum install -y gcc gcc-c make pcre-devel zlib-devel openssl-devel # Ubuntu / Debian 系列 apt update apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev这些依赖缺了哪个./configure阶段就会报相应的错误所以最好一次性装全。这里有个经验之谈不要为了省空间跳过任何依赖特别是OpenSSL-devel。之前有个同事装完Nginx才发现没有SSL模块后面补装折腾了快一个小时完全没必要。下载源码的话建议直接去Nginx官网找到下载地址用wget拉下来。国内机器访问官网速度不稳定可以考虑用国内镜像源。wget https://nginx.org/download/nginx-1.22.1.tar.gz tar -zxvf nginx-1.22.1.tar.gz cd nginx-1.22.1版本这里多说一句Stable版本偶数版本号比如1.22.x是生产环境的首选Mainline版本奇数版本号比如1.23.x功能更新但不建议生产用。这是很多老手血泪总结出来的不是随便说说。2.2 configure参数解析与选择./configure是整个编译安装的灵魂也是新手最容易迷茫的地方。网上的教程几乎清一色都是--prefix/usr/local/nginx配上几个默认参数但实际工作里参数的选择是要根据你的业务场景来的。我常用的一个配置参数组合是这样的./configure \ --prefix/usr/local/nginx \ --usernginx \ --groupnginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-stream \ --with-stream_ssl_module \ --with-pcre \ --with-cc-opt-O2 \ --with-ld-opt-Wl,-E拆开解释几个关键的--prefix指定安装目录。这个决定了后面所有配置文件的路径建议一开始就规划好不然后面改起来特别麻烦。--user和--group指定Nginx运行的用户务必不要用root跑Nginx。安全风险太大正确做法是先创建nginx用户让worker进程用普通用户身份运行。--with-http_ssl_moduleSSL模块做HTTPS必须要默认不编译进去这是新手最容易漏的。--with-http_v2_moduleHTTP/2支持现在网站性能优化绕不开HTTP/2建议加上。--with-stream四层TCP/UDP代理模块。如果你想用Nginx做TCP负载均衡比如代理MySQL、Redis没有这个模块是不行的。--with-http_stub_status_module提供/nginx_status页面可以查看连接数、请求数等基础监控指标。--with-cc-opt和--with-ld-opt这两个是给编译器和链接器的额外参数普通场景用我上面给的就够了不需要深究。2.3 make install与启动配置configure都通过了编译安装就轻松了就是两条命令make -j4 make installmake这一步是真正把源码编译成二进制的环节-j4参数是启用4个并行任务能显著加快编译速度。具体的数字根据你服务器的CPU核数来定如果是2核的机器就-j2别贪多。编译安装完成后Nginx会被装到/usr/local/nginx目录下二进制文件在sbin/nginx配置文件在conf/nginx.conf默认站点根目录在html/。这里有个很多人会忽略的问题编译安装的Nginx系统不会自动帮你创建init脚本或systemd服务。也就是说你没法用systemctl start nginx来启动只能手动执行sbin/nginx。这会带来两个麻烦一是开机不会自启二是进程挂了不会自动拉起。解决方案是自己写一个systemd unit文件放到/etc/systemd/system/nginx.service[Unit] Descriptionnginx - high performance web server Afternetwork.target [Service] Typeforking PIDFile/usr/local/nginx/logs/nginx.pid ExecStartPre/usr/local/nginx/sbin/nginx -t ExecStart/usr/local/nginx/sbin/nginx ExecReload/usr/local/nginx/sbin/nginx -s reload ExecStop/usr/local/nginx/sbin/nginx -s stop Usernginx Groupnginx [Install] WantedBymulti-user.target注意到Typeforking没有这个很关键。因为Nginx的master进程启动后会自动fork出worker进程service要等master起来才算启动完成。如果不这么写systemd可能会误判启动状态。写完保存执行systemctl daemon-reload然后用systemctl start nginx试试正常的话就说明服务配置好了。再用systemctl enable nginx设置开机自启。3. 系统包管理器安装高效省心的标准方案如果你不想编译或者对Nginx的定制需求不高那直接用系统的包管理器装就完事儿。这条路最省心五分钟搞定但有几个细节值得说道。3.1 CentOS/Ubuntu的安装方式CentOS系列默认的yum源里是有Nginx的但版本比较老。所以我更推荐先加一个Nginx官方源这样装到的版本比较新。# CentOS 7 rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm # CentOS 8/9 用 dn # 先创建一个 nginx.repo 文件 vi /etc/yum.repos.d/nginx.repo # 填入以下内容 [nginx] namenginx repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://nginx.org/keys/nginx_signing.key # 然后安装 yum install -y nginxUbuntu/Debian系列的操作逻辑类似也是先加源再安装# 先装依赖 apt install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring # 导入官方GPG key curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg /dev/null # 添加源 echo deb [signed-by/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu lsb_release -cs nginx | tee /etc/apt/sources.list.d/nginx.list # 安装 apt update apt install -y nginx装完之后Nginx二进制在/usr/sbin/nginx配置文件在/etc/nginx/下其中nginx.conf是主配置conf.d/目录里可以放零散的子配置sites-available/和sites-enabled/是Debian系的传统布局。注意一下这里和源码编译安装的目录区别后面操作容易搞混。3.2 包管理器的目录结构和配置习惯用包管理器装的Nginx配置风格和编译安装的版本差异挺大的。最大的区别是两个目录conf.d/你下发的额外配置可以放这里比如某个站点的server块。sites-available/sites-enabled/Debian系特有的“可用配置”和“启用配置”分离机制。配置文件先放到sites-available然后用软链接的方式在sites-enabled里启用。# 在 sites-available 里新建一个站点配置 vi /etc/nginx/sites-available/example.conf # 内容示例 server { listen 80; server_name example.com; root /var/www/example; index index.html; } # 启用这个站点 ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf # 测试配置并重载 nginx -t systemctl reload nginx很多新手容易在conf.d和sites-enabled哪个生效上懵掉。其实关键看nginx.conf里http块下的include指令把include了哪个目录打开看一眼就明白了。3.3 官方源和第三方源的选择建议有时候官方源的版本还是不能满足需求比如你用CentOS 7官方源里的是1.20.x但你想用1.22的新特性。这时候有两个选择方案一直接从官网下载新版RPM包来装wget https://nginx.org/packages/centos/7/x86_64/RPMS/nginx-1.22.1-1.el7.ngx.x86_64.rpm rpm -Uvh nginx-1.22.1-1.el7.ngx.x86_64.rpm方案二折腾一下第三方维护的源。这里不太建议在生产环境用因为第三方源良莠不齐有些和系统的兼容性没有经过充分测试。我见过不止一次因为用了不靠谱的源导致Nginx装完没法启动的情况。一句话总结能用系统自带源就用系统源能加官方源就加官方源别为了一个模块随便试第三方源。4. Docker容器化部署隔离与复用的现代化利器Docker部署Nginx是这几种方式里面最现代化的也是我最推荐在云原生架构下用的方案。它最大的好处就是环境隔离、部署一致、升级方便。但也因为多了一层容器抽象有些人初期会觉得不太适应。4.1 直接running一个Nginx容器如果你只是想快速体验一下Nginx一条命令就能搞定docker run -d --name mynginx -p 80:80 nginx这条命令做了什么-d表示后台运行--name给容器起个名字-p 80:80把宿主机的80端口映射到容器的80端口。跑完之后浏览器访问宿主机IP看到Nginx欢迎页就说明成功了。但这种方式有个问题容器删了你在里面改的配置就全没了。所以实际使用中一定要把配置文件挂载到宿主机上。一个标准的做法是# 先把 nginx 的默认配置拷到宿主机 mkdir -p /data/nginx/{conf.d,html,logs,ssl} docker cp mynginx:/etc/nginx/nginx.conf /data/nginx/nginx.conf docker cp mynginx:/etc/nginx/conf.d/default.conf /data/nginx/conf.d/default.conf # 删掉刚才的测试容器重新以挂载方式运行 docker rm -f mynginx docker run -d \ --name mynginx \ -p 80:80 \ -p 443:443 \ -v /data/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /data/nginx/conf.d:/etc/nginx/conf.d \ -v /data/nginx/html:/usr/share/nginx/html \ -v /data/nginx/logs:/var/log/nginx \ -v /data/nginx/ssl:/etc/nginx/ssl \ nginx:1.22-alpine几个关键点解释一下nginx:1.22-alpine是Alpine Linux版镜像体积小只有几十MB比完整版小了一个量级。生产环境推荐这么用既能满足需求又减少了攻击面。:ro表示只读挂载主配置文件只读是合理的防止容器里误改。conf.d目录别加ro因为后续可能要往里丢配置。日志目录一定要挂载出来。不然容器一删日志全没排查问题啥也看不到。4.2 docker-compose编排部署如果你一个项目要用到Nginx加后端服务单独跑docker run就有点管不过来了这时候docker-compose是更优雅的方案。我平时最常用的编排模板大概是这样version: 3.8 services: nginx: image: nginx:1.22-alpine container_name: web-nginx restart: always ports: - 80:80 - 443:443 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./conf.d:/etc/nginx/conf.d - ./html:/usr/share/nginx/html - ./logs:/var/log/nginx - ./ssl:/etc/nginx/ssl networks: - webnet depends_on: - app app: image: your-app:latest container_name: web-app restart: always expose: - 8080 networks: - webnet networks: webnet: driver: bridge注意这个编排里app服务用的是expose而不是ports。什么意思呢expose只暴露给Docker内部网络访问宿主机访问不到。Nginx通过proxy_pass http://app:8080就能访问到后端的服务但外部流量只能从Nginx的80端口进来。这种设计是标准的一道入口Nginx对多个内部服务的架构安全性比直接把每个服务端口都暴露出来好得多。启动方式就一条命令docker-compose up -d日常维护命令也放在这里# 重启 Nginx 容器 docker-compose restart nginx # 重新加载配置通过信号 docker exec web-nginx nginx -s reload # 查看 Nginx 日志 docker-compose logs -f nginx # 更新镜像后重建 docker-compose up -d --build4.3 容器化部署的注意事项Docker部署Nginx看起来简单实际上有几个容易踩的坑。第一个坑是容器里的时间问题。默认情况下Docker容器用的是UTC时间而不是宿主机的本地时间。这会导致Nginx日志里记录的时间比实际时间慢8个小时。排查问题的时候日志时间对不上能让人疯掉。解决办法很简单运行的时候加上时区挂载-v /etc/localtime:/etc/localtime:ro或者通过环境变量指定-e TZAsia/Shanghai第二个坑是容器内的Nginx用户权限问题。官方Nginx镜像默认以root启动master进程worker进程切换为nginx用户。如果你挂载配置文件目录时权限设置不对比如宿主机的目录所有者是root容器内nginx用户可能没有权限读取导致启动失败或者404。我的习惯是直接在挂载的宿主机目录上设置宽松权限chown -R 101:101 /data/nginx101是nginx在官方镜像里的UID和GID这么设置保证容器内可以正常读写。第三个坑是镜像版本和宿主机内核的兼容性。比如在CentOS 7上用比较新的nginx镜像有时会遇到IPv6或防火墙相关的问题。这时候优先检查模块加载情况不行就换用兼容性更稳的Alpine系镜像。5. 三种安装方式的对比与切换指南把三种方式都跑通之后我在实际选择时的参照系也就清晰了。这里做一个直接的横向对比供参考。维度源码编译包管理器Docker安装速度慢需编译快下载即装快拉镜像即用定制性高可加任意模块低受限于源中可挂载定制升级难度高重编译低yum update低换镜像目录结构自定义标准路径容器隔离对宿主机影响占用编译依赖安装系统包几乎无影响适配新手程度不友好友好中等适合场景生产定制、内网部署快速部署、学习微服务、云原生5.1 从包管理器迁移到编译安装的注意点很多人的成长路径是先yum install装了Nginx用了一段时间后发现需要加模块于是决定迁移到编译安装。这个迁移过程有几个坑需要注意端口冲突编译版和包管理器版都监听80端口必须先停掉旧服务再启动新的。配置迁移包管理器版的主配置在/etc/nginx/nginx.conf编译版在/usr/local/nginx/conf/nginx.conf直接把文件复制过去不一定可用。因为路径变了、用户可能不同、include的目录结构也不一样。systemd管理切换包管理器版的Nginx自带systemd服务编译版需要手动创建工作。迁移时要先把旧的systemd服务停掉并禁用再启用新写的unit文件。# 停掉旧的包管理器版 systemctl stop nginx systemctl disable nginx # 备份旧配置 cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak # 编译安装新版本然后启动新的 systemd 服务 systemctl start nginx5.2 平滑升级与版本回退技巧Nginx最让我喜欢的一个特性就是平滑升级这也是它在生产环境里地位不可撼动的重要原因。用源码编译方式升级时这个优势体现得淋漓尽致。# 假设你是源码编译安装当前在 /usr/local/nginx/nginx-1.20 # 下载新版源码比如 1.22解开后进入目录重新编译 ./configure --prefix/usr/local/nginx --with-http_ssl_module make -j4 # 不要 make install先把新二进制备份然后替换旧二进制 cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old cp ./objs/nginx /usr/local/nginx/sbin/nginx # 给运行中的旧 master 发送 USR2 信号让它启动新的 master kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid) # 关闭旧的 worker 进程 kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)执行完这一套流程Nginx进程已经被无缝切换到新版本期间不会中断任何一个请求。这是Nginx设计最优雅的地方比很多动不动就要重启的服务不知道高到哪里去了。如果新版本有问题想回退就反过来# 给新的 master 发送 HUP 信号重载配置然后恢复旧二进制 mv /usr/local/nginx/sbin/nginx.old /usr/local/nginx/sbin/nginx kill -HUP $(cat /usr/local/nginx/logs/nginx.pid)5.3 卸载Nginx要彻底说完了安装和升级卸载也是绕不开的话题。尤其是你从包管理器版切到编译版或者反过来卸载不彻底会留下各种暗坑。yum安装的卸载yum remove nginx # 或者 rpm -e nginx编译安装的卸载由于没有统一的卸载命令需要手动清理# 停掉服务 /usr/local/nginx/sbin/nginx -s stop # 或者 kill 掉 master 进程 # 删除安装目录 rm -rf /usr/local/nginx # 清理 systemd 服务文件 rm -f /etc/systemd/system/nginx.service systemctl daemon-reload # 清理 nginx 用户 userdel nginxDocker部署的卸载就简单多了docker stop mynginx docker rm mynginx docker rmi nginx:1.22-alpine卸载时最容易遗漏的就是残留配置文件和日志。包管理器版删掉之后/etc/nginx目录可能还在编译版删掉后/usr/local/nginx目录可能没删干净。重新安装时如果遇到奇怪的问题优先检查这些老配置是不是被新的实例加载了。6. 常见问题与排查技巧实录最后这部分我根据自己的实践和接触过的案例把安装Nginx时最容易遇到的问题整理成一个速查表希望能帮你省去一些排查时间。问题现象可能原因解决方法./configure报缺少PCRE/zlib/OpenSSL依赖包未安装安装pcre-devel zlib-devel openssl-devel编译报undefined reference错误模块之间依赖关系没处理好检查--with参数去掉冲突模块nginx -t报配置错误配置文件语法有误逐段检查用nginx -t -c指定配置文件测试启动失败端口被占用80端口被Apache或其他服务占用netstat -tlnp查占用进程改Nginx监听端口想用HTTPS但配置报unknown directive sslnginx二进制未编译SSL模块重新编译加上--with-http_ssl_module或者换包管理器版Docker容器无法访问外部端口防火墙未放行或端口未映射检查docker -p参数firewall-cmd放行端口Docker挂载配置后404挂载目录权限不对设置chown -R 101:101宿主机目录日志时区差8小时容器默认UTC时间挂载/etc/localtime或设TZAsia/Shanghaiaccess_log显示Permission deniedNginx用户无权限写日志目录检查日志目录权限改为nginx用户或调整权限6.1 编译安装的依赖排查方法编译安装时报依赖缺失是最高频的坑。排查的方法很简单就是看./configure输出的最后几行错误信息。比如这行checking for PCRE library ... not found ./configure: error: the HTTP rewrite module requires the PCRE library.它明确告诉你缺少PCRE。但这里有个迷惑性的点报的是“library”而不是“development headers”。有些时候你明明装了pcre包但缺少pcre-devel也会报这个错。解决办法就是把devel包一并装上。还有一个技巧如果编译时提示缺少某个lib但用yum install又找不到对应的包可以用yum provides */头文件来搜索哪个包提供了这个文件。yum provides *pcre.h这样会列出所有包含pcre.h的软件包找到对应的devel包装上去就行。6.2 systemd服务启动异常的处理思路编译Nginx后手动写的systemd service启动时最容易出问题。我常用的排查流程是这样的# 1. 查看服务状态和错误信息 systemctl status nginx # 2. 检查日志 journalctl -u nginx # 3. 手动执行看具体报错 /usr/local/nginx/sbin/nginx -t最常见的问题是PIDFile路径写错了。Nginx编译安装后默认PID文件在logs/nginx.pid相对prefix路径也就是/usr/local/nginx/logs/nginx.pid。systemd里写错路径会导致服务状态判断错误。解决办法就是在unit文件里和nginx.conf里都确认PID路径保持一致。6.3 Docker容器退出状态排查实录跑Docker版Nginx时如果容器启动就退出通常先看日志docker logs mynginx常见错误有几种端口占用宿主机80端口已经有个Nginx在跑了Docker再绑定就会冲突。这时候要么停掉宿主机Nginx要么改Docker的端口映射。配置文件挂载错误你挂载了宿主机上的一个nginx.conf但这个文件语法有问题Nginx启动时检测失败直接退出。解决方法是先在宿主机上用docker镜像跑一下nginx -tdocker run --rm -v /data/nginx/nginx.conf:/etc/nginx/nginx.conf:ro nginx:1.22-alpine nginx -t这个命令很实用相当于用容器内的Nginx二进制来校验挂载的配置有没有语法错误。文件权限问题挂载了日志目录或证书文件但权限不够容器的worker进程无法写入/读取。这种情况日志里会有Permission denied类似的提示。写在最后的经验之谈安装Nginx这件事情本身没有多难真正的价值在于搞清楚每条命令背后的逻辑和取舍。我自己从一开始只会yum install nginx到后来为了加模块去学编译参数再到用Docker做自动化部署中间踩过不少坑但也正是这些坑让我对Nginx的理解越来越深。如果让我给一个最直接的建议那就是不管用哪种方式安装先把nginx -t、reload、logs这几个基本操作练熟。安装只是开始后面的配置和运维才是真正考验人的地方。配置文件的语法错误排查、日志的解读、反向代理的调试这些才是让Nginx真正发挥价值的工作。一个新装的Nginx跑起欢迎页不算本事能稳定承载线上流量才是水平。最后再分享一个我个人的小习惯安装完Nginx后第一时间把nginx -V的输出保存下来标注好日期。这个命令会显示编译时用了哪些参数、添加了哪些模块。等哪天忘了自己装的是什么配置或者需要排查诡异问题的时候这串信息能省不少事。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进