Nginx 三件套实战:源码编译、零停机平滑升级与性能调优(附完整配置与信号时序)
环境基线:Rocky Linux 10 + Nginx 1.26.x(文中所有命令实测可用,请按你的发行版调整) 标签:Nginx / 性能调优 / Linux / 运维 / 反向代理
目录
- 一、为什么非要源码编译
- 二、编译安装:从 configure 到 systemd
- 三、平滑升级:零停机换版本(信号时序)
- 四、性能调优:从进程模型到内核参数
- 五、常见问题 QA(实战踩坑合集)
- 六、结语
一、为什么非要源码编译
线上 Nginx 还是老版本?想加一个 lua 或 echo 模块,却发现发行版仓库版装不了第三方模块?大版本升级又不敢停业务?这篇把三个问题一次解决。
先回答第一个:为什么放弃 dnf install nginx 而选源码编译。
| 第三方模块 | ❌ 无法挂载(需静态编译进核心) | ✅ –add-module 自由扩展 |
| 版本选择 | 跟随发行版仓库(通常滞后) | 任意官方 release |
| 编译参数 | 固定 | 可定制路径、日志、PID、模块 |
| 升级控制 | 依赖仓库 | 完全自主(配合平滑升级) |
| 维护成本 | 低 | 高(需自行管理依赖与更新) |
结论:追求稳定省心用软件源;需要模块扩展或深度定制,选源码编译。本文按「编译安装 → 平滑升级 → 性能调优」三段展开,每段都能直接落地。
二、编译安装:从 configure 到 systemd
2.1 环境准备
# 编译依赖:gcc/make + PCRE2(正则)/zlib(gzip)/openssl(TLS)
dnf install -y gcc make pcre2-devel zlib-devel openssl-devel git tar
# 创建运行用户(worker 进程最小权限运行)
id nginx &>/dev/null || useradd -r -s /sbin/nologin nginx
⚠️ 依赖务必一次装齐:缺 pcre2-devel 会在编译时报错,缺 openssl-devel 无法启用 SSL 模块——而这类错误要到 make 阶段才暴露。
2.2 configure:参数即架构决策
cd /usr/local/src/nginx-1.26.2
./configure \\
–prefix=/usr/local/nginx \\ # 安装基准目录
–sbin-path=/usr/sbin/nginx \\ # 可执行文件路径
–conf-path=/etc/nginx/nginx.conf \\ # 主配置路径
–error-log-path=/var/log/nginx/error.log \\
–http-log-path=/var/log/nginx/access.log \\
–pid-path=/var/run/nginx.pid \\ # PID 文件(systemd 依赖)
–lock-path=/var/run/nginx.lock \\
–user=nginx –group=nginx \\ # worker 进程身份
–with-http_ssl_module \\ # HTTPS
–with-http_v2_module \\ # HTTP/2
–with-http_realip_module \\ # 反代场景真实客户端 IP
–with-http_stub_status_module \\ # /status 监控指标
–with-http_gzip_static_module \\ # 预压缩静态资源
–add-module=/usr/local/src/echo-nginx-module # 第三方模块静态编译
关键认知:configure 是一次性"架构决策"——路径、模块清单在编译期定死,后期改路径要重新编译。参数务必进 Git/Ansible,保证可复现。
2.3 编译、安装与 systemd 托管
make -j$(nproc) && make install
tee /etc/systemd/system/nginx.service << 'EOF'
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target
[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t # 语法门禁:配置错则不启动
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable –now nginx
nginx -V # 确认 configure arguments
ss -tlnp | grep ':80' # 确认监听
systemd unit 三个要点:
⚠️ 版本号核实:网上不少教程出现 nginx-1.30.4 等非官方版本号(官方当前主线 1.27.x、稳定版 1.26.x)。生产请以 nginx.org 下载页 为准,警惕非官方 fork 与捆绑后门。
三、平滑升级:零停机换版本(信号时序)
3.1 为什么需要平滑升级
- 直接 systemctl restart 会中断存量 TCP 连接;
- 大版本升级有风险,需要可回滚;
- Nginx 原生信号机制支持新旧 Master 短暂并存、逐批切换——这就是"平滑"的根基。
3.2 信号速查表
| HUP | 重载配置(平滑 reload) | 改配置后生效,不停机 |
| USR1 | 重新打开日志文件 | 日志轮转 |
| USR2 | 启动新 Master(新旧并存) | 升级新版本二进制 |
| WINCH | 旧 Master 优雅关闭 worker | 升级后排空旧连接 |
| QUIT | 优雅退出(排空后退出) | 关闭服务 / 升级收尾 |
| TERM/INT | 快速退出 | 紧急停止 |
3.3 升级五步法(附信号时序)
# Step 1: 备份旧二进制 —— 用 mv 不用 cp!
mv /usr/sbin/nginx /usr/sbin/nginx.old
# Step 2: 编译新版本(configure 参数保持一致),覆盖安装
cd /usr/local/src/nginx-1.27.4
./configure [与旧版完全相同的参数]
make -j$(nproc)
cp objs/nginx /usr/sbin/nginx
# Step 3: USR2 —— 旧 Master 拉起新 Master(新旧并存)
kill -USR2 $(cat /var/run/nginx.pid)
# Step 4: WINCH —— 旧 Master 关闭全部旧 worker(存量连接排空)
kill -WINCH $(cat /var/run/nginx.pid.oldbin)
# Step 5: 确认稳定后 QUIT —— 退出旧 Master
kill -QUIT $(cat /var/run/nginx.pid.oldbin)
时序示意:
旧 Master ──USR2──▶ 新 Master 启动(新旧并存,同监听 80/443)
旧 worker ──WINCH─▶ 排空存量连接后优雅退出
新 worker 持续处理新连接
旧 Master ──QUIT──▶ 全部退出,升级完成
3.4 回滚方案
# 新版本有问题:用旧二进制接管
kill -HUP /usr/sbin/nginx.old
kill -QUIT $(cat /var/run/nginx.pid) # 退出新 Master
3.5 两个高频坑
参考:Nginx 官方控制文档(Signals)
四、性能调优:从进程模型到内核参数
原则:先测后调、单变量验证、以业务指标为准。以下为通用起点值,请用压测数据验证调整。
4.1 进程与连接模型(瓶颈核心)
# 全局块
worker_processes auto; # auto = CPU 核数;IO 密集可 1.5~2×核数
worker_rlimit_nofile 65535; # worker 可打开文件数上限(与 ulimit 联动)
# events 块
events {
worker_connections 4096; # 单 worker 最大并发连接
multi_accept on; # 一次 accept 多个新连接
use epoll; # Linux 默认 epoll
}
并发估算:最大并发 ≈ worker_processes × worker_connections。注意 worker_connections 包含客户端 + upstream + FastCGI 所有连接,纯反代场景实际可用客户端数要打折。
为什么 epoll 是根基(对比三种 IO 模型):
- BIO(Apache prefork):一连接一线程,并发=线程数,内存爆炸;
- select/poll:O(n) 遍历全部 fd,万级连接时每次唤醒扫 1 万次;
- epoll:O(1) 事件驱动,内核只通知活跃连接——这就是 Nginx 高并发的秘密。
4.2 传输与 IO 调优
http {
sendfile on; # 零拷贝:内核直接发文件
tcp_nopush on; # 配合 sendfile 攒满包再发(大文件吞吐)
tcp_nodelay on; # 禁 Nagle(小包低延迟)
keepalive_timeout 65; # 客户端长连接保持时间
client_max_body_size 64m; # 上传限制(按业务调)
}
4.3 压缩与缓存
gzip on;
gzip_vary on;
gzip_comp_level 4; # 4~6 为性价比区间,越高 CPU 越贵
gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml;
# 静态资源强缓存(文件名带 hash 时可用 immutable)
location ~* \\.(jpg|jpeg|png|gif|css|js|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off; # 静态资源不记日志,减 IO
}
4.4 反代场景(upstream)调优
upstream backend {
ip_hash; # 会话有状态(验证码等)保持粘性
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
keepalive 32; # 复用与后端空闲连接,减少握手
}
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1; # upstream keepalive 必需 HTTP/1.1
proxy_set_header Connection ""; # 清空 Connection 头才能复用
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s; # 快速失败,防连接堆积
proxy_read_timeout 30s;
}
4.5 内核参数(进阶,务必压测验证)
# /etc/sysctl.d/99-nginx.conf
net.core.somaxconn = 65535 # listen 队列上限
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535 # 反代出站端口池(高并发反代必须)
net.ipv4.tcp_tw_reuse = 1 # 快速复用 TIME_WAIT 端口
sysctl -p /etc/sysctl.d/99-nginx.conf
⚠️ 内核参数影响全局,重启后才会生效,先在测试环境压测验证再上线;tcp_tw_reuse 仅适用出站连接(客户端/反代侧)。
4.6 调优验证三板斧 + 实测对比
# 1. 确认进程模型生效
ps aux | grep 'nginx: worker' | grep -v grep | wc -l # 应 = worker_processes
# 2. 压测(ab 单机快速验证)
ab -n 100000 -c 1000 -k http://localhost/ | grep -E 'Requests per second|Failed requests|Non-2xx'
# 3. 监控指标(启用 stub_status)
# location /status { stub_status; }
curl http://localhost/status
调优前后对比(请填入你实测的数据):
| Requests per second | (占位:实测填入) | (占位:实测填入) | – |
| Failed requests | (占位) | (占位) | – |
| 平均响应时间 | (占位) | (占位) | – |
指标解读:Reading/Writing 高说明未完成请求多(调 worker_connections 或查慢后端);Waiting 高是空闲 keepalive 多(正常)。
五、常见问题 QA(实战踩坑合集)
Q1:nginx -t 报 Permission denied? 非 root 执行导致:[emerg] open() "/var/run/nginx.pid" failed (13: Permission denied)。用 sudo nginx -t 校验;并把日志/pid 目录属主对齐:chown -R nginx:nginx /var/log/nginx。systemd unit 里 ExecStartPre 以 root 自动校验,可彻底规避。
Q2:./configure 报 no …/config was found? 第三方模块源码未正确获取(目录缺失/下载不完整),configure 找不到模块的 config 描述符。重新 git clone –branch <tag> 稳定版(禁止 master),并 ls -la <module>/config 确认后再 configure。
Q3:反代 502 / 验证码不显示? 502 = Nginx 无法把请求转发到后端。三板斧:ps aux | grep 后端进程 → ss -tlnp | grep 8080(是否监听)→ tail /var/log/nginx/error.log | grep upstream。EL9 别忘了 SELinux:getenforce 为 Enforcing 时 setsebool -P httpd_can_network_connect 1。
Q4:为什么升级备份必须 mv 而不是 cp? 见 3.5 节:cp 覆盖运行中的二进制,USR2 升级时旧进程句柄指向被替换文件可能崩溃;mv 保留旧文件可用于回滚。
Q5:worker_connections 设了 4096 为什么还扛不住? 该值包含客户端 + upstream + FastCGI 全部连接;且要确认 worker_rlimit_nofile 与系统 ulimit -n 联动调大,否则 fd 先耗尽。
六、结语
源码编译给你模块自由,平滑升级给你变更自由,性能调优给你容量自由——三者构成完整的 Nginx 生命周期管理能力。两条铁律收尾:
参考
- Nginx 官方文档:https://nginx.org/en/docs/
- Nginx 信号与控制:https://nginx.org/en/docs/control.html
- echo-nginx-module(OpenResty 生态):https://github.com/openresty/echo-nginx-module
- 压测工具:ApacheBench(ab)、wrk、hey
本文首发于个人技术博客,欢迎转载注明出处。


