用包管理器装 Nginx 确实省事,apt install nginx 一行命令就完事。但很多个人站长迟早会碰到包管理器解决不了的需求:想给 Nginx 加上某个官方源里没有的第三方模块,想用最新的 HTTP/3 特性而发行版仓库还停在老版本,想把某些用不到的模块砍掉以减小内存占用,或者干脆就是被发行版"默认不带 Brotli"这件事卡住。这时候唯一的出路就是从源码编译。本文把从零编译安装 Nginx 的完整流程讲透,重点不是"照着敲命令",而是让你理解每一步在做什么、参数该怎么选、以及升级时怎样做到不断线。
先搞清楚:为什么要编译,以及代价是什么
发行版仓库里的 Nginx 是别人替你编译好的二进制,它的编译参数是固定的。你可以在服务器上执行 nginx -V 看到它的完整 configure 参数,通常长这样:
nginx -V
# nginx version: nginx/1.18.0 (Ubuntu)
# built with OpenSSL 1.1.1f 31 Mar 2020
# TLS SNI support enabled
# configure arguments: --with-cc-opt='-g -O2 ...' --with-ld-opt='...'
# --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf
# --with-compat --with-http_ssl_module ...注意那些 --with-* 开关。仓库版为了通用性,通常开了绝大多数模块,但第三方模块一个都没有,而且版本由发行版的发布周期决定——Debian 稳定版里的 Nginx 可能落后主线好几个大版本。
编译安装的代价你要心里有数:一是不再有 apt upgrade 帮你升级,以后每次升级都得自己重编译;二是路径和包管理器版本不一样,systemd 单元、日志切割、证书钩子都要重新对齐;三是如果编译参数写错,可能出现"能启动但某功能不生效"的隐蔽问题。所以我的建议是:只有当你确实需要自定义模块或新特性时才编译,否则优先用官方 apt 源(nginx.org 提供的官方仓库其实也带 Brotli,能省掉大部分编译需求)。
第一步:安装编译依赖
编译 Nginx 需要 C 编译器、make,以及三个运行时库的开发头文件:pcre(正则,用于 location 匹配和 rewrite)、zlib(gzip 压缩)、openssl(HTTPS)。在 Debian/Ubuntu 上:
apt update
apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev如果你的 configure 参数里要用 --with-http_v3_module(HTTP/3),还需要额外的 QUIC 相关库,通常要自己编译 BoringSSL 或用带 QUIC 补丁的 OpenSSL。对大多数站长来说,先不要碰 HTTP/3,把基础的 SSL 编译跑通再说。
第二步:下载源码并选定版本
cd /usr/local/src
curl -O https://nginx.org/download/nginx-1.25.4.tar.gz
tar zxf nginx-1.25.4.tar.gz
cd nginx-1.25.4版本选择上,稳定分支(1.24.x、1.26.x 这种偶数第二位)比主线分支更适合生产。主线分支功能新但变动快,个人站没必要追新。去 nginx.org 的下载页能看到当前稳定版是哪个。
如果要用第三方模块,模块源码也要一并下载。比如给日志加 JSON 格式的 nginx-module-json、提供更多响应头操作的 headers-more-nginx-module、实现文件合并的 nginx-http-concat。以 headers-more 为例:
cd /usr/local/src
git clone https://github.com/openresty/headers-more-nginx-module.git还有一类模块是编译进源码树的,加之前要先 patch 打补丁,这种对新手不友好,先跳过。
第三步:configure —— 最关键的一步
configure 决定了最终二进制的全部能力。下面是一份我常用的、兼顾功能与精简的参数模板:
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/local/nginx/sbin/nginx \
--conf-path=/usr/local/nginx/conf/nginx.conf \
--pid-path=/usr/local/nginx/logs/nginx.pid \
--error-log-path=/usr/local/nginx/logs/error.log \
--http-log-path=/usr/local/nginx/logs/access.log \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-stream \
--with-stream_ssl_module \
--with-threads \
--add-module=/usr/local/src/headers-more-nginx-module逐条解释这些关键参数:
--prefix决定安装根目录,我用/usr/local/nginx,和包管理器版本的/etc/nginx完全隔离,防止混用。--with-http_ssl_module是 HTTPS 的前提,不加这个就算装了 OpenSSL 也没法配证书,这是最常见的编译失误。--with-http_v2_module提供 HTTP/2;--with-http_realip_module在套 CDN 后还原真实客户端 IP,几乎是标配。--with-http_gzip_static_module允许直接返回预压缩好的.gz文件,比运行时压缩省 CPU。--with-http_stub_status_module开一个状态页,方便接监控。--with-stream让 Nginx 能做 TCP/UDP 四层代理,配合--with-stream_ssl_module可以做数据库端口的 TLS 卸载。--with-threads启用线程池,处理大量磁盘 IO(比如静态大文件)时能显著降低阻塞。--add-module=是挂载第三方模块的入口,路径必须是解压后的模块源码根目录。
如果 ./configure 输出末尾出现 ./configure: error: ... not found,基本就是上面的依赖没装全,回去补 *-dev 包即可。configure 成功后会打印一段 Configuration summary,务必扫一眼确认你要的模块都是 + 而不是 -。
第四步:编译与安装
make -j$(nproc)用 -j$(nproc) 让所有 CPU 核心并行编译,速度能快好几倍。编译过程通常一两分钟,结束时如果最后一行是 make[1]: Leaving directory ... 而没有红色 Error,就成功了。接着安装:
make install不要习惯性地跑 make install 之外的清理命令,源码目录要留着——以后升级、加模块都靠它。
装完先做一次语法自检,再启动:
/usr/local/nginx/sbin/nginx -t
/usr/local/nginx/sbin/nginxnginx -t 会告诉你配置文件语法是否正确、路径是否可写,这是启动前必须的一步。启动后用 curl -I 127.0.0.1 看看有没有返回 HTTP/1.1 200。
第五步:用 systemd 接管,别裸奔
源码安装的 Nginx 默认没有 systemd 单元,重启服务器后不会自动拉起。自己写一个 /etc/systemd/system/nginx.service:
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target
[Service]
Type=forking
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
PrivateTmp=true
[Install]
WantedBy=multi-user.target然后 systemctl daemon-reload && systemctl enable --now nginx。这里几个细节容易踩坑:Type=forking 和 PIDFile 必须和 configure 时的 --pid-path 一致;ExecReload 用 -s reload 实现平滑重载;ExecStop 用 -s quit 而非 -s stop,前者等待现有连接处理完,更优雅。
第六步:平滑升级不断线
这是编译安装最核心的价值场景。当你要升级 Nginx 版本或加个模块时,重编译后可以用信号量做"热替换":先启动新 master 进程,再让旧进程优雅退出。流程如下:
# 1. 新版本编译好,二进制在 objs/nginx
# 2. 用当前运行的 nginx 发送 USR2 信号,启动新 master
kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)
# 3. 此时新老两个 master 并存,新 master 会在原 pid 基础上加后缀
ls /usr/local/nginx/logs/nginx.pid* # 会看到 nginx.pid 和 nginx.pid.oldbin
# 4. 确认新进程正常后,向旧 master 发送 WINCH(优雅关闭 worker)
kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)
# 5. 观察一段时间没问题,彻底关闭旧 master
kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)如果升级后发现新版本有问题想回滚,在旧 master 还没被 QUIT 之前,向旧 master 发 HUP 重新拉起它的 worker,再向新 master 发 QUIT,流量就切回去了。这套信号机制是 Nginx 相对 Apache 的经典优势,编译安装时你自己掌控二进制,才能真正用上它。
真实复盘:一次加模块导致全站 502 的排查
我当初给自己的图片站加 nginx-http-concat 模块(用来把多个 CSS/JS 合并成一个请求),configure 加了 --add-module 后编译安装、reload。reload 返回成功,但访问首页立刻大量 502。
排查动作:先看 error.log,报的是 worker process ... exited with code 1;再看 nginx -t 竟然还是通过的。问题出在——模块的加载是在 worker 进程真正处理请求时触发的,配置语法检查查不出模块内部运行时的动态库依赖。我 ldd objs/nginx | grep "not found" 一看,模块依赖了一个打包版系统里没有的库版本。
重新 make 前先补全依赖、并且对比了 ldd 输出才敢 install。教训是:加第三方模块后,别只看 nginx -t,一定要在低峰期先用一个测试 server_name 验证,或者直接在 objs/nginx 阶段就 ldd 检查动态库完整性。补充一句,make install 前备份一份旧二进制(比如 cp sbin/nginx sbin/nginx.bak)也是保命操作,出问题可以立刻换回。
常见误区小结
- 忘了
--with-http_ssl_module:配了 443 端口却报the "ssl" directive is not allowed here,就是这个原因。 - 用
make install覆盖了正在运行的二进制:直接换文件可能导致运行中的进程行为异常,正确做法是重编译后走信号量热升级,或至少reload。 - configure 里
--prefix和 systemd 单元不一致:导致nginx -t找不到配置、启动失败,是新手最常见的一类"明明装好了却起不来"。 - 以为编译安装就万能:其实很多需求(Brotli、HTTP/3)用 nginx.org 官方 APT 源就能满足,官方源同样支持 Brotli,能不编译就不编译。
总结
源码编译安装 Nginx 的本质,是用"手动维护的复杂度"换取"对版本和模块的完全控制"。流程可以浓缩成五步:装依赖 → 下源码 → 精心 configure → make && make install → systemd 接管。其中 configure 参数决定能力边界,systemd 单元决定可维护性,信号量热升级决定能不能不中断地升级。对个人站长而言,如果你只是想要一个能跑起来的 Web 服务器,包管理器永远是第一选择;但一旦你开始被"某个模块装不上""版本太老"卡住,这份编译流程就是必须掌握的基本功。记住两条铁律:编译前先备份配置和二进制,升级永远走信号量热替换不走覆盖,你就能在享受自定义能力的同时,把风险控制在可回滚的范围内。