
我最早接触Nginx时是在一台CentOS 7服务器上照着源码编译教程一步步敲命令。当时觉得凡事自己编译才显得专业结果make等了快十分钟后来为了加模块、做升级又折腾了好几个晚上。再往后我在几十台不同环境Ubuntu、CentOS、Docker集群上陆续部署过Nginx才彻底想明白一个道理源码编译、包管理器、Docker这三种方式不算三选一而是不同场景下的不同答案。这篇把我在这三种方式上踩过的坑、整理好的完整流程、以及最后沉淀下来的选型经验全部写下来无论你是刚入门Linux的新手还是已经负责生产环境的运维都能找到可以直接照做的路径。1. 三种安装方式背后的逻辑先选场景再动手1.1 源码编译到底在编译什么很多刚接触源码编译的人会疑惑明明apt install nginx一条命令就行为什么还有人要费劲去编译答案其实很简单——Nginx的核心能力是由编译时静态打入的模块决定的。你用apt或yum装到的Nginx模块组合是发行版维护者决定的。常见的ssl、gzip、proxy、stream这些模块一般都会带上但第三方模块比如Lua、以及各种非官方扩展就没办法通过包管理器直接加装。如果业务需要这些能力只能走源码编译在configure阶段静态把模块编进二进制里。源码编译的另一层价值是“可控”。你可以通过--prefix指定安装位置通过--with-xxx精确启用模块通过--without-xxx剔除不需要的模块。但它同时意味着编译和依赖都要自己打理升级要自己处理卸载也没有统一入口。1.2 包管理器凭什么成为生产默认发行版维护者已经把依赖关系、配置目录、开机启动、日志轮转这些问题都处理好了。你apt install nginx之后系统会自动创建nginx用户、把服务注册进systemd、配置好日志目录。这省下的不只是安装那几分钟而是后面每一次升级都能走统一的系统更新通道和系统其他软件包一起维护。需要提醒的是很多发行版自带仓库里的Nginx版本偏旧。比如Debian stable仓库里可能是1.18而官方主线已经到1.25以上。对大多数业务来说旧几个版本问题不大但如果你的功能依赖新版特性或者在意安全修复就需要用Nginx官方提供的apt/yum源。1.3 Docker方式解决的不是安装问题而是隔离问题用Docker跑Nginx严格来说更像“运行”而不是“安装”。它解决的问题是环境一致性和隔离。同一台机器上跑多个不同配置的Nginx实例互不影响或者用docker-compose一次性编排Nginx加PHP加MySQL的环境这在本地开发和CI测试里特别顺手。但换来的是配置管理上的额外复杂度。最典型的就是配置文件挂载问题我自己在alpine镜像上就踩过挂载conf.d目录后容器启动失败的坑后面第4节会专门展开讲。先把三种方式的差别放在一张表里后面每个部分再逐个细说对比项源码编译包管理器(apt/yum)Docker容器安装速度慢需要编译秒级完成取决于镜像拉取速度模块定制完全可控由发行版或官方源包决定默认模块固定定制复杂服务托管手写systemd或手动管理自动集成systemddocker start/stop升级方式手动替换二进制apt upgrade / yum update拉新镜像重建容器卸载手动清理一条命令基本干净删容器和镜像适合场景特殊模块、老旧环境、源码研究绝大多数生产环境多实例隔离、CI/CD、快速试验2. 源码编译安装全流程从依赖准备到systemd托管2.1 编译前依赖怎么装在Debian/Ubuntu系统上编译Nginx需要的依赖可以用一条命令装齐sudo apt update sudo apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev在CentOS/RHEL系sudo yum install -y gcc make pcre-devel zlib-devel openssl-devel这四样缺一不可。build-essential或者gcc提供编译工具链libpcre3-dev对应Nginx的正则表达式支持location匹配、rewrite规则全依赖它zlib是gzip压缩模块的底层库openssl是SSL/TLS支持的底层库。如果你编译过PHP、Redis这类C项目会发现这套依赖几乎是标配。2.2 configure参数先想清楚要哪些功能从nginx.org官网下载页面拿源码包生产环境建议选Stable稳定版追求新功能再考虑Mainline主线版。下载解压后进入源码目录执行./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-stream \ --with-stream_ssl_module \ --with-http_gzip_static_module \ --with-http_stub_status_module \ --usernginx \ --groupnginx几个参数逐个说下--prefix安装根目录默认是/usr/local/nginx。这个路径最好一开始就定死因为编译时很多路径会写进二进制后面再改很麻烦。--with-http_ssl_module必选。没有它配置HTTPS时会直接报不支持ssl指令我见过不少老服务器就是因为编译时漏了它后面只能重新编译替换二进制。--with-http_v2_moduleHTTP/2支持现代Web服务基本都会用到。--with-stream四层TCP/UDP代理模块。如果你要用Nginx做MySQL、Redis这类服务的反向代理这个模块必须有。很多人搜“nginx反向代理tcp最大连接数”配置的就是这个stream块。--with-http_stub_status_module提供一个/nginx_status接口输出连接数统计监控Nginx必备。configure阶段最常见的报错是“the HTTP rewrite module requires the PCRE library”这类本质就是缺了对应的开发包把2.1里的依赖装齐就能解决。configure通过后终端会输出当前启用的模块列表记得瞄一眼确认ssl和stream都在列表里。然后开始编译make -j4 sudo make installmake -j后面的数字是并行编译线程数按CPU核数来。整台4核的测试机编一次大概几分钟但如果是1核1G的云服务器建议老老实实用make -j2不然编译过程很容易因为内存不足直接OOM。编译安装完的目录结构是这样的/usr/local/nginx/ ├── conf/ # 配置文件目录对应包管理方式下的/etc/nginx ├── html/ # 默认静态页面 ├── logs/ # 日志目录注意不是/var/log/nginx └── sbin/nginx # 唯一的主程序2.3 源码安装后的接管写一个systemd服务文件源码安装默认不会注册systemd服务直接nginx启动的话既没有开机自启也没有程序崩溃后的自动拉起。建议自己写一个服务单元[Unit] DescriptionThe NGINX HTTP and reverse proxy server Afternetwork.target remote-fs.target nss-lookup.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 quit PrivateTmptrue [Install] WantedBymulti-user.target注意Typeforking因为Nginx主进程会fork出worker进程systemd需要靠PIDFile来跟踪主进程。这个路径要和编译时的prefix对应写错了服务会反复启动失败。写完放到/etc/systemd/system/nginx.service然后sudo systemctl daemon-reload sudo systemctl enable --now nginx sudo systemctl status nginx如果启动失败用journalctl -u nginx看日志。十有八九是nginx -t阶段报错按提示改配置就好。2.4 源码编译方式的升级与卸载源码编译最麻烦的就在这。升级通常要下载新版本源码用和之前完全相同的configure参数重新编译但不要直接make install覆盖。标准做法是先把旧二进制备份再把新编译出的objs/nginx拷贝过去mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old cp -r /usr/local/nginx/conf /usr/local/nginx/conf.bak # 在新源码目录 make 之后 cp objs/nginx /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx -s reload这样旧worker会继续用旧二进制处理存量连接新连接走新二进制其实就是Nginx官方推荐的平滑升级流程。至于源码编译的卸载说实话Nginx的Makefile里没有make uninstall目标只能手动删/usr/local/nginx目录、清理自建的service文件和nginx用户。这也是我后来在大部分生产环境放弃源码编译的现实原因装起来折腾卸起来更折腾。3. 包管理器安装三分钟上手但版本和目录别搞混3.1 Debian/Ubuntuapt装的是哪个包在Debian系执行sudo apt install nginx装的其实是一个元包系统会按发行版默认策略拉取具体实现。在Debian上通常默认装nginx-fullUbuntu上可能是nginx-core。不同变体的区别在于模块集合大小nginx-core是最小功能集nginx-full是常见模块全包含nginx-light是精简版nginx-extras会额外带一些第三方模块。装完以后最大的困惑点是目录布局。Debian系Nginx的主配置在/etc/nginx/nginx.conf里面靠底部的include把/etc/nginx/conf.d/目录以及/etc/nginx/sites-enabled/目录都加载进来。如果你从源码方式切换过来习惯性往conf.d里丢配置是没问题的但Debian默认站点入口在sites-enabled而nginx.org官方构建版的默认站点放在conf.d/default.conf里。这两套布局不要混着来不然容易出现“我改了配置为什么没生效”的乌龙。另一个大问题是apt源里的Nginx版本通常偏旧。想用官方版本可以引入nginx.org提供的apt仓库sudo tee /etc/apt/sources.list.d/nginx.list EOF deb https://nginx.org/packages/ubuntu/ jammy nginx EOF注意把jammy换成你的Ubuntu发行版代号Debian对应的写法是bookworm、bullseye这类。然后导入官方GPG key再apt update。这里要特别提醒启用官方源之后apt upgrade可能会把发行版自带的Nginx替换成官方构建版配置目录会从sites-enabled那套布局切到官方版conf.d布局。升级过程中系统会问是否保留现有配置这时候不要直接一路回车要先想清楚自己现在用的到底是哪套布局。3.2 CentOS/RHELEPEL、官方源和SELinuxCentOS 7及更早版本默认yum仓库里没有Nginx最常见做法是先装EPELsudo yum install -y epel-release sudo yum install -y nginxEPEL里的Nginx版本比官方慢不少想用官方源就在/etc/yum.repos.d/nginx.repo里写[nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://nginx.org/keys/nginx_signing.keyCentOS用户还要额外注意SELinux。默认Enforcing模式下如果你改了Nginx的监听端口、或者让Nginx读取非默认目录里的静态文件很可能出现服务明明起来了、访问却一直被拒绝的情况日志里全是Permission denied。处理方式要么用semanage去调整对应的sebool要么在确认安全评估通过的前提下临时放行。这个坑在包管理器方式下特别典型因为源码编译环境下很多人直接就把SELinux关了反而遇不到。yum/dnf安装后的目录布局和Debian类似但又有差异主配置是/etc/nginx/nginx.conf配置碎片放在/etc/nginx/conf.d/默认站点目录是/usr/share/nginx/html日志在/var/log/nginx/。CentOS下装完默认会有一个监听80端口的server块返回测试页面。3.3 systemd管理与nginx -t的救场作用不管apt还是yum装完直接sudo systemctl start nginx sudo systemctl enable nginx然后不管改任何配置文件都先养成跑nginx -t的习惯。这个命令会检查语法错误、server块冲突、证书文件路径是否存在。它在“替换SSL证书不生效”“修改反向代理规则”这些场景里尤其重要——先确认配置没有问题再排查其他环节能少走很多弯路。包管理器的卸载也是最干净的# Debian/Ubuntu sudo apt purge nginx # CentOS sudo yum remove nginx注意purge会连同配置文件一起删除remove只删程序保留/etc/nginx。有生产配置的话卸载前一定先备份。4. Docker方式安装Nginx镜像、挂载与reload的细节4.1 镜像怎么选Docker Hub上的官方nginx镜像有两类tag值得分清默认的nginx:latest基于Debian体积较大。nginx:stable对应稳定版分支。nginx:stable-alpine基于Alpine Linux体积会小很多官方Debian版镜像一百多MBalpine版五十MB上下部署拉取速度快不少。但alpine不是无脑选。它底层是musl libc如果要往镜像里编译第三方模块或者安装一些依赖gcc编译的扩展会更麻烦。如果只是用默认模块做静态站点、反向代理、SSL终结alpine完全够用。如果预计以后要往镜像里塞编译模块选Debian系更省心。另外一个容易被忽略的点是官方镜像里的/etc/nginx/nginx.conf已经include了/etc/nginx/conf.d/*.conf而默认的首页配置就存放在/etc/nginx/conf.d/default.conf里。4.2 基本运行与配置挂载部署一个带自定义配置的Nginx容器标准的做法是这样docker run -d --name nginx-web --restartalways \ -p 80:80 -p 443:443 \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /data/nginx/html:/usr/share/nginx/html:ro \ -v /data/nginx/cert:/etc/nginx/cert:ro \ nginx:stable-alpine这里把宿主机三个目录分别挂载成容器里的配置目录、站点目录、证书目录。:ro表示只读挂载是个好习惯防止容器内进程意外改动配置文件。挂载模式下改配置只需要改宿主机上的文件然后让Nginx重新加载配置docker exec nginx-web nginx -t docker exec nginx-web nginx -s reload这个方式比每次docker restart优雅得多和宿主机上直接执行nginx -s reload语义一致不会中断正在处理的连接。4.3 alpine挂载conf.d报错一个真实且高发的坑“nginx alpine挂载conf.d报错”是搜索热度很高的词我自己也踩过。场景通常是宿主机建好了一个conf.d目录里面放了自定义配置文件然后挂载进alpine容器结果容器启动就报错nginx: [emerg] open() /etc/nginx/conf.d/default.conf failed (13: Permission denied)或者更隐晦一点日志里一直提示某个配置文件找不到。根因通常有两类。第一类是权限问题。Alpine镜像里的Nginx进程默认以nginx用户运行uid是101。宿主机上你手工创建的文件owner可能是root而且权限是600或者目录没有给其他用户读权限。容器内nginx用户读不了Nginx直接emerg退出。解决办法很直接宿主机上把配置文件权限至少改成644目录权限改成755。如果挂载的是日志目录这种需要写文件的还需要让宿主机的目录属主和容器内nginx用户的uid匹配。第二类是挂载目录覆盖掉了镜像里的默认文件。镜像里原本有/etc/nginx/conf.d/default.conf你把一个空目录挂载上去会把它盖掉。如果nginx.conf里又引用了某些缺失的文件启动就会失败。稳妥的规避方法是挂载单个文件而不是整个目录例如-v /data/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro如果确实要挂载整个目录在挂载前先把镜像里对应的默认文件复制到宿主机目录里再改动。遇到这类问题想快速定位可以docker exec -it nginx-web sh进到容器里看看容器内到底能看到哪些文件、权限是什么。记住容器内看到的权限来自宿主机文件的uid映射单纯在容器里chown是解决不了的必须改宿主机上的文件属性。4.4 Docker模式下的证书更新与卸载Docker下替换SSL证书有个常见错觉宿主机上改了证书文件容器会立刻自动用新证书。实际要看挂载方式。如果用的是bind mount上面那种-v挂载目录宿主机证书文件确实实时同步进容器但Nginx进程在启动或上次reload时已经把证书读进内存了所以必须主动reload。如果当初是用docker cp把证书复制进容器、没有挂载那reload之后虽然生效但容器一旦重建就会丢——很多人更新证书后当时好用重启完又变回旧证书就是这个原因。要让证书持久生效一定要把证书目录放到宿主机并挂载进容器。卸载Docker部署的Nginxdocker stop nginx-web docker rm nginx-web docker rmi nginx:stable-alpine配置如果只存在于容器内、没有同步到宿主机挂载目录删除容器等于删掉配置。这一点想清楚比任何命令都重要。5. 安装完先过一遍验证清单再谈SSL证书和卸载5.1 不管哪种方式装完先做这5步验证一套五分钟的验证流程能避免90%的“装完了怎么访问不了”nginx -v确认版本号再用nginx -V大写看编译参数和已加载模块。nginx -t确认配置正常以后每次改配置后都做一次。systemctl status nginx确认进程在跑Docker场景用docker ps。curl -I http://127.0.0.1能看到HTTP/1.1 200或302就是正常。curl本机通但外网不通基本就是防火墙或云平台安全组的问题和Nginx无关。ss -lntp | grep nginx确认80和443端口在监听。5.2 替换SSL证书不生效的排查链路“nginx替换ssl证书不生效”这个搜索词热度一直不减说明踩的人很多。我梳理一下自己的排查顺序每次都能定位。第一步检查文件层。确认nginx.conf里的ssl_certificate和ssl_certificate_key路径与证书文件实际位置完全一致包括相对路径的基准目录。nginx对相对路径是相对于编译时的prefix目录源码方式或/etc/nginx包管理方式最容易出错。证书文件存在、路径正确、权限可读这些都用nginx -t验证。第二步确认证书和私钥是匹配的同一对。执行openssl x509 -noout -modulus -in server.crt | openssl md5 openssl rsa -noout -modulus -in server.key | openssl md5两个md5一致才说明证书和私钥配套。如果私钥设了密码openssl rsa命令会交互式询问可以在命令后加-passin pass:你的密码。第三步直接看服务器实际返回的证书openssl s_client -connect 你的域名:443 -servername 你的域名输出的证书信息能立刻确认问题出在Nginx层还是客户端缓存层。还有些证书是链证书需要把站点证书和中间证书拼成一个文件fullchain只塞站点证书会导致部分手机客户端校验失败。第四步reload而不是restart。修改SSL配置后首选nginx -s reload不要轻易整个进程重启。reload后curl看到的还是旧证书就要查是不是有多个server块冲突、SNI配置错误、或者前面还有负载均衡/CDN缓存了旧证书。浏览器端的缓存和HSTS也会让人以为“没换证书”建议开隐私窗口验证。第五步Docker特殊检查。确认证书是通过挂载进容器的而不是docker cp进去的然后docker exec nginx-web nginx -s reload。5.3 卸载Nginx但把配置留下来很多人搜“linux系统卸载nginx”是因为卸载完才发现没备份。生产服务器上卸载前建议先sudo systemctl stop nginx sudo cp -r /etc/nginx /etc/nginx.bak.$(date %F) sudo tar czf nginx-cert-backup.tar.gz /etc/ssl/certs然后把备份目录挪到安全位置再执行卸载。源码编译安装的清理要手动删/usr/local/nginx目录、删掉自建的systemd service文件并daemon-reload如果编译时指定了--usernginx还得userdel。这也是我一直坚持包管理器默认的原因卸载行为可预期。6. 三种方式不是三选一我的环境判断顺序与落地习惯6.1 同一个项目里用过三种方式说说我怎么切换的拿我维护的一个接口中台项目举例。最开始只有一台2C4G云主机业务简单我直接用apt装Nginx做静态文件服务和SSL终结五六分钟搞定后续升级全走apt。后来业务要内网打通一套基于TCP长连接的消息推送需要Nginx做四层代理。发行版仓库里模块虽然齐全但版本对不上我要用的配置写法于是我去启用Nginx官方apt源升级到当时的新稳定版stream模块开箱即用几乎没折腾。差不多同期另一个团队要在一个Nginx里集成Lua做灰度路由官方源和发行版源里都没有这个模块只能源码编译。我们把configure参数完整记录在部署文档里编译产物放进制品库。之后每次重建服务器都按同一套参数来几年下来升级过两次靠的就是有据可查。本地开发环境又是另一回事。前端同事要起一个和线上一样的反代环境直接docker run挂载同样的conf.d目录起停都在容器里宿主机干干净净。同一张架构图三种安装方式各管一段谁也不用说服谁。6.2 安装完成不代表交付完成基础加固清单我见过太多装完Nginx就直接暴露到内网的服务器。不管用哪种方式安装落地到项目前先把这几项做掉server_tokens off关闭版本号暴露。很多自动化扫描工具就是靠banner里的Nginx版本去找对应漏洞的。改掉默认server块。不要让“Welcome to nginx”或者配置里遗留的example.com直接应答。我一般把默认server块直接return 444关掉连接或者指向一个明确的错误页。确认access_log和error_log路径可达日志切割配好。包管理器安装一般自带logrotate配置源码编译安装要自己写一个不然logs目录在高流量下会无限膨胀。worker_processes auto配合worker_connections配置连接数。搜“nginx反向代理tcp最大连接数”的最后多半会发现瓶颈不在Nginx配置而在系统文件描述符限制ulimit -n和内核的somaxconn参数。安装后顺手看一眼这两个值能省很多事。6.3 三条坚持了很多年的操作习惯如果只给刚入行的自己一句话我会说先让安装方式匹配你的运维体系而不是匹配某个教程看起来的硬核程度。团队有统一配置管理和升级流程就用包管理器。项目特殊到需要静态编译第三方模块就把源码编译流程标准化。环境频繁起停、需要多实例隔离就直接容器化。没有哪种方式在所有场景下都最优但一定有一种方式在你当前场景下最省心。我个人的默认顺序是生产环境首推官方源加包管理器安装Docker留给开发和测试源码编译只在明确需要额外模块时才用。另外不论最终选了哪种方式装完立刻做的三件事永远是nginx -t验证、备份默认配置、记录版本和编译参数。这三件事加起来不到五分钟却能让未来的自己少熬好几个夜。这个小习惯坚持下来比任何安装技巧都值钱。等安装方式从“每次摸索一次”变成“标准化操作流程”之后Nginx在你的项目里就真正成了透明的基础设施。