一、问题背景
Nginx 性能优化这件事,多数团队都干过,但真正能做得对的不到一半。我先后参与过几次“调优后并发翻倍”的实战,回头翻改动清单会发现,真正起作用的其实只有 6 到 8 个配置项,再加上系统层的 2 到 3 个内核参数。剩下 90% 的动作,基本是在堆配置、抄博客,既没有基准数据,也没有压测验证,更没有回滚方案。
我在 2019 年做过一次比较完整的 Nginx 调优:一台 8 核 16G 的机器,跑 1 个 Nginx 加 1 个后端 Java 应用。优化前压测是 4800 QPS,p99 顶到 60ms;优化后同样压测条件,QPS 跑到 9200,p99 降到 22ms。看着是“翻倍”,但事后拆解,实际是 4 个层面一共 12 个改动点叠加出来的结果,单独拎出任何一项,都不足以称之为翻倍。
之所以写这篇文章,是因为我把当时的调优笔记整理到团队 wiki 后,很多同学追着问“能直接把那台机器的 Nginx 配置发我一份吗”。答案是不能简单复制:我当时同时改了系统层、配置层、Keepalive 层、压缩层和缓存层。只有把这几层拆开讲清楚,读者才有可能在自己生产环境里复现类似收益。
先解释一下“并发翻倍”这个说法。并发能力不是单一指标,它至少包含 5 个维度:
- 峰值 QPS:同样延迟下能扛多少 QPS。
- 稳态 QPS:持续 1 小时、错误率低于 0.1% 时的 QPS。
- p99 延迟:99% 请求的响应时间。
- 错误率:5xx / 4xx 比例。
- 资源利用率:CPU、内存、连接数、fd、I/O。
真正的“翻倍”,是这 5 个维度同时改善,而不是只盯着 QPS 一个数。全文所有数据都基于同样的压测场景,例如:
wrk -t8 -c200 -d60s -H "Connection: keep-alive" http://host/
最后先给一个明确观点:优化前一定要做基线(Baseline)。没有基线的优化,说白了就是“感觉上快了”,但拿不出证据说服别人。基线怎么做?留 1 小时稳态压测,再配合关键指标快照,后面会细说。
二、适用场景
本文适合以下读者和场景:
- 初中级运维 / SRE:生产环境 Nginx 在跑但并发上不去,想知道还能怎么调。
- 后端开发:需要本地起 Nginx 反向代理做接口压测。
- 架构师:要给新业务从零开始上线 Nginx,需要配置清单。
- DevOps 工程师:在 CI/CD 里集成 Nginx 性能基线测试。
不适合的情况:
- 已经用 CDN 解决流量峰值问题,Nginx 优化的边际收益会下降。
- 想做“百万 QPS 集群”,本文聚焦单机/单实例优化,集群层还需要 LB、HPA、Circuit Breaker。
- 想直接拿“配置模板”抄,本文更多是方法论,具体配置要结合业务调整。
我常用的“调优边界”判断表如下:
- 单实例 QPS 目标 < 1 万:单机调优足够,先不集群。
- 单实例 QPS 目标 1 万 - 5 万:单机调优 + 内核参数 + Worker 调优。
- 单实例 QPS 目标 5 万 - 20 万:Worker 调优 + Keepalive + 上游 keepalive + 缓存。
- 单实例 QPS 目标 > 20 万:考虑多实例、Cluster、硬件层。
本文覆盖前 3 档,第 4 档只点出方向,不做展开。
三、核心知识点
3.1 Nginx 的进程模型
Nginx 是多进程 + 异步非阻塞模型:
- Master 进程:读配置、绑定端口、启动 Worker。
- Worker 进程:处理实际请求,每个 Worker 是单线程事件循环。
- Worker connections:每个 Worker 同时能处理的连接数。
- Worker CPU affinity:Worker 与 CPU 核的绑定。
性能调优的所有事情,都围绕“Worker 数量是否合理”和“Worker connections 是否够”展开。从 Linux 视角看,Nginx 的每个连接就是一个 fd,所以 fd 上限直接决定并发上限。
3.2 性能分层
我把性能分成 4 层,优化时需要按顺序来:
- 系统层(OS):内核参数、fd 上限、文件句柄、SOMAXCONN、TCP 缓冲区、网卡 offload。
- Nginx 主配置:Worker 数量、Worker connections、sendfile、TCP_NOPUSH、TCP_NODELAY、use epoll、open_file_cache。
- HTTP 层:Keepalive、gzip、缓存、客户端 body 缓冲、proxy_buffer、proxy_cache、upstream keepalive。
- 上游层:应用层调优、连接池、慢调用治理。
只改第 2 层不调第 1 层,收益很小;只改第 3 层不调第 2 层,瓶颈会卡在事件循环。
3.3 压测工具与基线
基线压测常用工具:
wrk:多线程异步压测,推荐,需要编译或通过包管理器安装。
ab(Apache Bench):简单,单进程,不要在高并发压测时使用。
siege:支持 HTTP/1.1 和 HTTP/2。
vegeta:Go 写的,支持 rate 模型。
k6 / locust:脚本化压测。
示例:
# 装 wrk(Ubuntu/Debian)
apt install wrk
# CentOS / RHEL
yum install wrk
# 或者从源码编译
git clone https://github.com/wg/wrk.git
cd wrk && make && make install
# 压测:8 线程,200 并发,持续 60 秒
wrk -t8 -c200 -d60s --latency http://10.0.0.10:8080/
# 输出:
# Running 30s test @ http://10.0.0.10:8080/
# 12 threads and 200 connections
# Thread calibration: mean lat.: 1.812ms
# Latency distribution
# 50% 2.10ms
# 75% 3.50ms
# 99% 12.40ms
# 1789432 requests in 30.00s, 0.95GB read
# Requests/sec: 59647.21
# Transfer/sec: 32.41MB
3.4 关键的“反模式”
下面这些是“看上去在优化,实际拖后腿”的反模式:
- 盲目把
worker_processes 调成 32,高核机器反而可能因锁竞争性能下降。
- 盲目开
gzip on + gzip_min_length 0,对小文件压缩收益为负。
- 把
keepalive_timeout 调成 0,等于关闭 Keepalive,性能可能掉一半。
proxy_buffer_size 调得超大,吃 Worker 内存,容易触发 OOM。
- 不对上游配
keepalive 连接池,每次请求新建 TCP 连接,又慢又吃 fd。
- 用
if 做 location 内的判断,经典 if is evil。
- 不分离
server 块,证书、路由、反代全混在一起,排查困难。
access_log 直接写 off,出事故时没有日志可查。
下面 14 节会逐条展开该怎么做。
四、整体排查或实施思路
整体思路总结为“5 步法”:
- 基线:压测现状,记录 QPS / p99 / 错误率 / CPU / 内存 / fd / 端口。
- 诊断:看瓶颈在哪一层,系统、Worker、Keepalive 还是上游。
- 最小改动:每次只改一个点或一层,改完跑同样压测。
- 固化:改动有效后,落到版本管理(Ansible / Salt / Helm)。
- 监控:基线 + 偏差告警。
下面是简化版诊断决策树:
性能上不去
├── CPU 100%?
│ ├── us 高 -> 上游慢(后端业务) / 业务逻辑重 -> 优化业务
│ ├── sy 高 -> 锁 / 系统调用 / 中断 -> 排查内核
│ └── wa 高 -> 磁盘 I/O 慢 -> 排查点 4 已经在做
├── 内存满?
│ ├── Worker 数过多 -> 调小 worker_processes
│ └── 缓存过大 -> 调小 proxy_cache / 关闭 gzip on 大文件
├── 端口耗尽?
│ └── upstream keepalive 没开 -> 见 7.5
├── fd 耗尽?
│ └── Worker connections 太大 / fd 上限不够 -> 见 7.1
└── 看起来都不高,但 QPS 顶不上去?
└── 业务处理慢 -> 上游问题,不在本文范围
五、实战步骤
下面按“基线 → 系统层 → Nginx 主配置 → HTTP 层 → 上游层 → 验证”的顺序展开。
5.1 第一步:压测基线
目的:拿到调优前的“真值”,后续所有改动都和它对比。
命令:
# 准备 1 台压测机(不能是待测机器自己)
# 待测 Nginx:10.0.0.10:8080
# 装 wrk
apt install -y wrk
# 压测 1(短跑,看大致水位)
wrk -t8 -c200 -d30s --latency http://10.0.0.10:8080/
# 压测 2(长跑,看稳态)
wrk -t8 -c200 -d300s --latency http://10.0.0.10:8080/
# 压测 3(高并发,看是否能顶住)
wrk -t16 -c1000 -d60s --latency http://10.0.0.10:8080/
同时记录以下系统指标:
# CPU
sar -u 1 30
# 内存
sar -r 1 30
# 磁盘 I/O
iostat -xz 1 30
# 网络
sar -n DEV 1 30
# 端口
ss -s
# fd
cat /proc/sys/fs/file-nr
# Worker 进程状态
ps -eo pid,pcpu,pmem,rss,stat,comm | grep -E 'nginx|worker'
典型基线输出:
- QPS: 4800
- p50: 8ms
- p99: 60ms
- CPU 100%(
us 高)
- 内存 8G(占 50%)
- 端口峰值 8000
- 错误率 0%
判断:CPU 100% 是瓶颈,但 us 高,说明 CPU 在干用户态的事,需要进一步确认是“事件循环”被太多连接打满,还是“上游太慢”。
进阶基线方法:
- 多场景压测:
wrk 默认发 GET /,业务主链路往往是 POST,要写 Lua 脚本或用 wrk2、k6 模拟。
- 混合负载:线上 70% GET、20% POST、10% 其他,压测要按比例。
- 冷启动 vs 热启动:刚启动的 Nginx 缓存命中率低,要排除冷启动前 5-10 分钟。
- 错误率基线:0.1% 是经验值,业务可能 0.01% 才算正常。
基线数据保存:
# 把基线数据保存到文件,后续对比
mkdir -p /tmp/baseline
wrk -t8 -c200 -d60s --latency http://10.0.0.10:8080/ > /tmp/baseline/before.txt
# 同时记录系统指标
sar -u 1 30 > /tmp/baseline/cpu-before.txt
sar -r 1 30 > /tmp/baseline/mem-before.txt
iostat -xz 1 30 > /tmp/baseline/io-before.txt
5.2 第二步:系统层优化
目的:在 Nginx 之上,把事件循环的资源上限放大。
改动清单:
# /etc/sysctl.d/99-nginx-tuning.conf
# 1) 接受连接队列上限
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 2) 端口范围(应对大量 TIME_WAIT 复用)
net.ipv4.ip_local_port_range = 1024 65535
# 3) 加快 TIME_WAIT 回收
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# 4) TCP 缓冲区
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# 5) 连接跟踪(高并发需要)
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 7200
# 6) 文件句柄上限
fs.file-max = 2097152
# /etc/security/limits.d/90-nginx.conf
nginx soft nofile 1048576
nginx hard nofile 1048576
nginx soft nproc 65535
nginx hard nproc 65535
root soft nofile 1048576
root hard nofile 1048576
生效:
sysctl -p /etc/sysctl.d/99-nginx-tuning.conf
# limits 需要重新登录才生效,或者用 LimitNOFILE 在 systemd unit 里覆盖
验证:
sysctl net.core.somaxconn
ulimit -n
cat /proc/sys/fs/file-nr
风险:tcp_tw_reuse=1 只对主动出方向的连接复用,不影响入站;但不要同时开 tcp_tw_recycle(新版内核已移除,概念上也不要乱用)。nf_conntrack_max 设大需要保证内存够,每项 conntrack 大约占 300-400 字节。
回滚:
# 备份
sysctl -a > /tmp/sysctl.before.txt
# 回滚
sysctl -w net.core.somaxconn=128
sysctl -w net.ipv4.tcp_max_syn_backlog=128
# 等
深入:内核参数含义详解
下面解释每个参数的真实含义,避免抄博客却不知道所以然:
net.core.somaxconn:系统级 accept 队列长度,listen() 第二个参数上限。调大后,listen(sock, SOMAXCONN) 才有效;但 Nginx 自己也会传 backlog,要看实际调用。
net.ipv4.tcp_max_syn_backlog:SYN 队列长度(没完成三次握手的连接)。syncookies 启用后会失效。
net.ipv4.tcp_tw_reuse:主动发起新连接时,是否复用 TIME_WAIT 状态的端口。对服务端入站连接无效。
net.ipv4.tcp_fin_timeout:FIN_WAIT_2 状态超时,默认 60s,调小到 15s 可以让连接更快释放。
net.ipv4.tcp_rmem / tcp_wmem:TCP 接收/发送缓冲区三档(min/default/max),由内核自动调节范围。
net.core.rmem_max / wmem_max:应用层 setsockopt(SO_RCVBUF) 的上限。
net.netfilter.nf_conntrack_max:连接跟踪表项上限。conntrack 不是必须,但用了 iptables/nftables 规则就必须关注。
fs.file-max:系统级 fd 上限。
深度:net.ipv4.tcp_mem
tcp_mem 是系统级 TCP 缓冲区页数,高内存机器也要调:
# 查看当前值
cat /proc/sys/net/ipv4/tcp_mem
# 输出:每个数字是"页数"
# 调整
sysctl -w net.ipv4.tcp_mem='384000 512000 768000'
# 16G 内存机器经验值
深度:网卡 offload
# 看网卡是否开启 LRO / GRO / TSO
ethtool -k eth0 | grep -E 'lro|gro|tso'
# 关闭 TSO(某些场景有副作用)
ethtool -K eth0 tso off
# 关闭 GRO
ethtool -K eth0 gro off
风险提示:网卡 offload 调整要看业务场景,不能一刀切。
5.3 第三步:Nginx 主配置优化
目的:让 Worker 数量和事件循环参数匹配机器资源。
改动清单:
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # 让 Nginx 自动按 CPU 核数设 Worker
# worker_processes 8; # 也可以显式指定
worker_rlimit_nofile 1048576; # 每个 Worker 的 fd 上限
worker_cpu_affinity auto; # 自动绑定 Worker 到 CPU(需要 Nginx 1.9+)
# Worker 连接数:略大于理论值即可
events {
worker_connections 65535; # 每个 Worker 最大并发连接
multi_accept on; # 一个事件循环 accept 多个连接(高并发)
use epoll; # Linux 下用 epoll
accept_mutex off; # 高并发下关闭,新连接分发更快
}
关键点解释:
worker_processes auto:Nginx 会按 CPU 核数启动 Worker。一般经验:CPU 核数 <= 8,worker = 核数;CPU 核数 > 8,可以 worker = 核数 * 0.5 ~ 1,但要实测。
multi_accept on:一次事件循环里 accept 多个连接,降低 accept 系统调用频率。
accept_mutex off:Worker 之间不再用 accept 互斥锁,减少延迟。多 Worker 高并发下推荐关闭;单 Worker 无意义。
生效:
nginx -t
nginx -s reload
验证:
ps -eo pid,pcpu,pmem,rss,comm | grep nginx
# 应该看到 1 个 master,N 个 worker
ss -s
# 看连接数
风险:worker_processes auto 在 NUMA 机器上要结合 worker_cpu_affinity 才有意义;否则 Worker 可能分布在不同 CPU 域,内存访问开销变大。
回滚:
# 备份
cp /etc/nginx/nginx.conf /tmp/nginx.conf.bak
# 改 + reload
# 回滚
cp /tmp/nginx.conf.bak /etc/nginx/nginx.conf
nginx -s reload
深度:Nginx 进程模型详细
1 个 master 进程负责:
- 读 / 解析配置
- bind / listen 端口
- spawn Worker 进程
- 处理信号(HUP / QUIT / USR1 / USR2 / WINCH)
- 升级二进制(平滑升级)
N 个 Worker 进程负责:
- 实际处理连接
- 通过共享内存(ngx_shared_zone)协作
- Worker 之间不需要进程间通信(IPC)
进程间通信共享内存:Nginx 用 mmap + atomic 实现无锁共享,典型应用是 limit_req / limit_conn 的计数器、upstream 的健康状态、proxy_cache 的元数据。
为什么 Worker 是单线程:避免锁竞争。Nginx 通过事件循环异步处理多连接。
为什么 Worker 不是越多越好:
- 每个 Worker 占用内存(基础约 2-5MB),Worker 太多会浪费内存。
- 多 CPU 之间共享内存有开销,NUMA 机器尤其明显。
- 一般经验:Worker = CPU 核数(I/O 密集型可适当超)。
深度:worker_cpu_affinity 配置
# 手动绑定到 8 核
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
# 或者更直观
worker_cpu_affinity auto; # 自动绑定(需要 Nginx 1.9+)
深度:为什么 accept_mutex 关闭
accept_mutex on:Worker 之间用 accept 互斥锁,保证同一时刻只有一个 Worker 在 accept,减少“惊群”。
accept_mutex off:多个 Worker 同时 accept,新连接分发更快。现代 Linux 内核已经修复了 accept 惊群,所以可以关。
深度:为什么 multi_accept on
multi_accept off(默认):一次事件循环 accept 一个新连接。
multi_accept on:一次事件循环 accept 多个新连接,降低 epoll_wait 返回次数,提高吞吐。
- 适合高并发短连接场景;低并发长连接场景意义不大。
5.4 第四步:HTTP 层优化(Keepalive + gzip + 缓存)
目的:让“长连接 + 压缩 + 缓存”三件套在 HTTP 层把流量打回去。
改动清单:
http {
# === 基础 ===
include mime.types;
default_type application/octet-stream;
charset utf-8;
# === 关键:Sendfile / 零拷贝 ===
sendfile on;
tcp_nopush on; # 与 sendfile 配合,包合并
tcp_nodelay on; # 小包立即发送,降低延迟
# === 客户端 Keepalive ===
keepalive_timeout 65;
keepalive_requests 1000; # 单个连接最多处理 1000 个请求(避免被恶意占用)
# === 客户端请求体缓冲 ===
client_body_buffer_size 128k;
client_max_body_size 16m;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# === 长连接 + gzip + 缓存(略) ===
# (在 server/location 块里按需)
...
}
Keepalive 关键参数:
keepalive_timeout 65:客户端连接空闲 65 秒后关闭。0 表示禁用 Keepalive,不要用 0。
keepalive_requests 1000:单连接最多处理 1000 个请求后强制关闭,防止连接被“长占用”。
- 客户端 Keepalive 必须在
http {} 块里设置。
风险:keepalive_requests 设得太小,会让长连接频繁重建,反而性能下降;太大又会被单连接占用太多资源。1000 是常用值,要结合业务实际请求量调整。
5.5 第五步:上游层优化(upstream keepalive + buffer)
目的:让 Nginx 和上游服务之间的连接复用,避免每次新建 TCP 连接。
改动清单:
http {
# 上游连接池(关键!)
upstream backend {
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
keepalive 64; # 关键:与上游保持的连接数
keepalive_timeout 60s; # 上游空闲连接超时
keepalive_requests 1000; # 上游单连接最多请求数
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 关键:清除 close,启用 keepalive
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_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# buffer
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}
}
}
关键点解释:
keepalive 64:Nginx 与每个上游 Worker 之间保持 64 个空闲连接(总数 64,不是每上游 64)。
proxy_http_version 1.1:必须 HTTP/1.1,否则 Keepalive 不生效。
proxy_set_header Connection "":必须清掉 Connection: close,否则 Nginx 会主动关闭。
风险:keepalive 设太大,会消耗 fd 和内存;设太小,起不到连接池效果。经验值:keepalive = 上游数 * 16 ~ 64。生产调优要压测。
回滚:
# 删除 keepalive 相关行
# proxy_http_version 1.0
# proxy_set_header Connection close;
# reload
5.6 第六步:压缩层优化
目的:让 gzip 真正起作用,而不是成为负担。
改动清单:
http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5; # 1-9,5 是经验值
gzip_min_length 1024; # 小于 1KB 不压缩(收益为负)
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
application/x-font-ttf
application/vnd.ms-fontobject
image/svg+xml
image/x-icon
font/opentype;
# 不要压缩已经压缩过的类型:image/jpeg, image/png, image/gif, video/mp4
}
关键点解释:
gzip_min_length 1024:小于 1KB 的内容不压缩。压缩小文件的开销大于收益。
gzip_comp_level 5:1-9 数字越大压缩比越高,但 CPU 越费,5 是经验值。
gzip_types 必须显式列,*不能只写 ``**,否则会压缩图片、mp4。
gzip_vary on:让响应带 Vary: Accept-Encoding,让 CDN / 浏览器能正确处理。
风险:gzip 会让 Worker 进程在压缩时短暂占用 CPU,高并发下可能让 us 升高。要压测,不能照搬。
回滚:
# 备份 /etc/nginx/nginx.conf
sed -i 's/gzip on;/gzip off;/' /etc/nginx/nginx.conf
nginx -s reload
5.7 第七步:缓存层优化
目的:让“短期重复请求”直接由 Nginx 响应,不打后端。
改动清单:
http {
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=my_cache:10m # 内存中 key 索引,10M 约 80K 个 key
max_size=10g # 磁盘最大 10G
inactive=60m # 60 分钟未访问清除
use_temp_path=off;
server {
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 10m; # 200/302 缓存 10 分钟
proxy_cache_valid 404 1m; # 404 缓存 1 分钟
proxy_cache_key "$scheme$proxy_host$request_uri";
add_header X-Cache-Status $upstream_cache_status; # 调试用
}
}
}
关键点解释:
levels=1:2:缓存文件目录层级,避免单目录文件过多。
keys_zone=my_cache:10m:key 索引放在内存,10M 大约能存 80K 个 key。
max_size=10g:缓存磁盘最多 10G,满了按 LRU 淘汰。
proxy_cache_valid:按状态码分别设置缓存时间。
风险:缓存会让“用户看到的内容”和“实际后端数据”有时差,业务必须接受这个时差;支付、库存、个人中心等场景不建议开启。可以按 location 细粒度开启。
回滚:
# 关闭缓存
sed -i 's/proxy_cache my_cache;/\/\/ proxy_cache my_cache;/' /etc/nginx/nginx.conf
nginx -s reload
5.8 第八步:日志层优化(避免 log IO 拖慢请求)
目的:access log 写磁盘是阻塞 IO,在高并发下会拖慢请求。
改动清单:
http {
# 异步日志 + 缓冲
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'urt="$upstream_response_time"';
# buffer 32k,flush 5s
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
# 减少不必要字段
open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;
}
关键点解释:
buffer=32k flush=5s:Nginx 把日志先写 buffer,5 秒 flush 一次,减少 IO 次数。
open_log_file_cache:缓存日志文件描述符,避免每次 open。
风险:开启 buffer 后,日志丢失窗口是 5 秒(进程崩溃 / kill -9 会丢)。async 模式可以异步写,但需 Nginx 1.9.5+。
回滚:
# access_log 去掉 buffer 和 flush
# access_log /var/log/nginx/access.log main;
nginx -s reload
5.9 第九步:安全 / 限制层优化
目的:让恶意流量或慢攻击被早期识别,不影响正常请求。
改动清单:
http {
# 限制连接数和请求速率
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
server {
limit_conn conn_per_ip 50;
limit_req zone=req_per_ip burst=20 nodelay;
}
}
关键点解释:
limit_conn:限制单个 IP 的并发连接数。
limit_req:限制请求速率,burst=20 nodelay 允许短时突发 20 个请求,超出的直接 503。
风险:阈值要结合业务调整,设太小会误伤正常用户。
回滚:
# 注释 limit_req / limit_conn 行
nginx -s reload
5.10 第十步:open_file_cache 优化
目的:高并发下 Nginx 要 open 大量静态文件,缓存 fd 减少 open 系统调用。
改动清单:
http {
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
关键点解释:
max=10000:最多缓存 10000 个文件 fd。
inactive=30s:30 秒未访问清除。
min_uses=2:被访问 2 次以上才缓存。
errors on:错误也缓存,避免每次都 stat 不存在的文件。
风险:如果文件被频繁修改(比如 CDN 推送),inactive=30s 会让客户端拿到旧 fd,要根据业务调整。
回滚:
# 注释 open_file_cache 相关行
nginx -s reload
5.11 第十一步:压测验证
目的:用同样的压测,看 QPS / p99 / 错误率变化。
命令:
# 同基线一样的命令
wrk -t8 -c200 -d60s --latency http://10.0.0.10:8080/
# 输出对比:
# Before:
# Requests/sec: 4800
# p99: 60ms
# Transfer/sec: 2.4MB
# After:
# Requests/sec: 9200
# p99: 22ms
# Transfer/sec: 4.5MB
判断逻辑:
- QPS 提升 50% 以上 + p99 下降 50% 以上:成功。
- QPS 提升但 p99 没变:可能是下游没有压力,只是事件循环打满了。
- QPS 没变但 p99 下降:可能是请求分布改善。
- 错误率 > 0.1%:有副作用,看 nginx error log。
5.12 第十二步:固化
目的:把这次调优的“结果”变成“标准”,不要下次重新调一遍。
操作:
# 把 nginx.conf 纳入 git
cd /etc/nginx
git init
git add .
git commit -m "baseline: 2024 nginx tuned config"
# 把 sysctl 配置纳入配置管理(Ansible / Salt / Puppet)
# /etc/sysctl.d/99-nginx-tuning.conf
# /etc/security/limits.d/90-nginx.conf
# 把压测脚本纳入 CI
# wrk 脚本 + 期望值(QPS > 8000, p99 < 30ms)
# 任何 PR 改 nginx.conf 后跑一次
# 把基线指标纳入监控
# Prometheus: nginx_connections_active, nginx_http_requests_total
# 告警: QPS 跌 30% 持续 5 分钟
5.13 第十三步:监控 + 告警
目的:调优完成后,要能“看到优化是否还生效”。
指标:
nginx_connections_active:活跃连接数。
nginx_connections_reading / writing / waiting:状态分布。
nginx_http_requests_total:总请求数,rate() 可得 QPS。
nginx_upstream_response_time_seconds:上游响应时间。
nginx_upstream_connect_time_seconds:上游连接时间。
告警规则示例(只是模板,要按业务调整):
# Prometheus alerting rules
groups:
- name: nginx
rules:
- alert: NginxConnectionsHigh
expr: nginx_connections_active > 50000
for: 5m
- alert: NginxQPSLow
# 业务基线是 8000 QPS,跌到 5000 持续 5 分钟
expr: rate(nginx_http_requests_total[5m]) < 5000
for: 5m
- alert: NginxUpstreamTimeHigh
expr: nginx_upstream_response_time_seconds > 0.5
for: 5m
验证告警:
# 故意发大请求,触发告警
wrk -t8 -c500 -d120s http://10.0.0.10:8080/
# 5 分钟后看 Prometheus 触发告警
六、常用命令
下面是一页纸速查,适合贴到工作台。
# === 看 Nginx 编译参数 ===
nginx -V
# 重点:--prefix, --with-http_v2_module, --with-http_gzip_static_module
# === 配置语法检查 ===
nginx -t
# === 优雅重载 ===
nginx -s reload
# === 关闭 ===
nginx -s quit # 优雅退出
nginx -s stop # 立即关闭(等同 kill TERM)
# === 看版本 ===
nginx -v
nginx -V
# === 看当前连接数 ===
ss -s
ss -tlnp | grep -E ':80|:443'
ss -tn | grep ESTAB | wc -l
# === 看 Worker 状态 ===
ps -eo pid,ppid,pcpu,pmem,rss,stat,comm | grep nginx
# === 看 access log 实时 ===
tail -f /var/log/nginx/access.log
# 排查 5xx
tail -f /var/log/nginx/access.log | awk '$9 ~ /^5/'
# === 看 error log ===
tail -f /var/log/nginx/error.log
grep -i 'error\|warn' /var/log/nginx/error.log | tail -50
# === 压测 ===
wrk -t8 -c200 -d60s --latency http://10.0.0.10:8080/
ab -n 10000 -c 100 http://10.0.0.10:8080/
siege -c 200 -t 60s http://10.0.0.10:8080/
# === 模拟客户端 ===
curl -v -H 'Host: example.com' http://10.0.0.10/
# 看 connection 是否复用
curl -v --keepalive-time 60 http://10.0.0.10/
# === 看上游状态 ===
# 启用 stub_status
# location /nginx_status {
# stub_status on;
# access_log off;
# allow 127.0.0.1;
# }
# curl http://127.0.0.1/nginx_status
# 输出:
# Active connections: 12
# server accepts handled requests
# 12345 12345 67890
# Reading: 0 Writing: 2 Waiting: 10
七、配置示例
下面是几个“贴上去就能用”的配置示例,涵盖主配置、HTTPS、上游、缓存。
7.1 主配置 nginx.conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 1048576;
worker_cpu_affinity auto;
pid /var/run/nginx.pid;
events {
worker_connections 65535;
multi_accept on;
use epoll;
accept_mutex off;
}
http {
include mime.types;
default_type application/octet-stream;
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
client_body_buffer_size 128k;
client_max_body_size 16m;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
application/x-font-ttf
application/vnd.ms-fontobject
image/svg+xml
image/x-icon
font/opentype;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
include /etc/nginx/conf.d/*.conf;
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header 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_set_header X-Forwarded-Proto $scheme;
}
}
7.3 upstream 块
upstream backend {
least_conn; # 负载均衡策略
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
keepalive 64;
keepalive_timeout 60s;
keepalive_requests 1000;
}
7.4 缓存 server 块
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=my_cache:10m
max_size=10g
inactive=60m
use_temp_path=off;
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$proxy_host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
}
}
7.5 静态文件 server 块
server {
listen 80;
server_name cdn.example.com;
root /var/www/static;
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|ttf)$ {
expires 30d;
access_log off;
add_header Cache-Control "public, max-age=2592000";
}
location / {
try_files $uri $uri/ =404;
}
}
7.6 限流 server 块
server {
listen 80;
server_name api.example.com;
limit_conn conn_per_ip 50;
limit_req zone=req_per_ip burst=20 nodelay;
location / {
proxy_pass http://backend;
}
}
7.7 灰度发布(server 块级别)
# 90% 流量走 stable
# 10% 流量走 canary
upstream stable {
server 10.0.0.11:8080;
}
upstream canary {
server 10.0.0.12:8080;
}
split_clients $request_id $upstream {
90% stable;
10% canary;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://$upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
7.8 简单 WAF 防护(allow/deny 黑白名单)
server {
listen 80;
server_name example.com;
# 黑名单 IP 段
include /etc/nginx/blocklist.conf;
deny all; # 默认拒绝
}
7.9 WebSocket 反向代理
upstream ws_backend {
server 10.0.0.11:8080;
keepalive 64;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location / {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s; # WS 长连接,读超时设大
}
}
7.10 gRPC 反向代理
upstream grpc_backend {
server 10.0.0.11:50051;
keepalive 64;
}
server {
listen 80;
server_name grpc.example.com;
location / {
grpc_pass grpc://grpc_backend;
grpc_set_header Host $host;
grpc_read_timeout 60s;
}
}
7.11 Logrotate 配置
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 nginx nginx
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
生效方式:logrotate -f /etc/logrotate.d/nginx(强制)。
风险:kill -USR1 让 Nginx 重开日志文件,不重启进程;如果 Nginx 没在,postrotate 会报错但不致命。
7.12 systemd unit(以防用了 systemd)
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=1048576
LimitNPROC=65535
生效:
systemctl daemon-reload
systemctl restart nginx.service
八、日志或指标观察方法
调优完成后,要能“看得到”。
8.1 关键日志
- access log:
/var/log/nginx/access.log,记录每次请求的 URI、状态、响应时间、上游响应时间。
- error log:
/var/log/nginx/error.log,记录 Nginx 内部错误、配置错误、上游不可达等。
- 系统日志:
/var/log/messages、/var/log/syslog 记录内核层面。
8.2 关键指标
| 指标 |
来源 |
关注 |
| QPS |
rate(nginx_http_requests_total) |
与业务基线对比 |
| p99 latency |
nginx_upstream_response_time_seconds |
< 100ms(经验值) |
| 错误率 |
rate(nginx_http_requests_total{status=~"5.."}) |
< 0.1% |
| 活跃连接 |
nginx_connections_active |
与 ulimit 比例 |
| Worker CPU |
ps + cpu |
80% 警戒 |
| fd 使用 |
cat /proc/sys/fs/file-nr |
与 fs.file-max 比例 |
| 上游响应时间 |
nginx_upstream_response_time |
慢调用治理 |
8.3 指标采集方式
- stub_status:Nginx 自带,简单文本指标。
- ngx_http_api_module(Nginx Plus):完整 API,商业版才有。
- VTS(nginx-module-vts):开源,带 Prometheus 集成。
- nginx-prometheus-exporter:Prometheus 官方 exporter。
- OpenTelemetry Nginx module:OTel 集成。
8.4 关键观察窗口
调优前后,看 1 小时、24 小时、7 天三个时间窗:
- 1 小时:看瞬时 QPS / 错误率。
- 24 小时:看周期(白天 / 夜间 / 早晚高峰)。
- 7 天:看趋势(是否回归)。
8.5 实时日志分析技巧
# 看 5xx 占比
awk '$9 ~ /^5/' /var/log/nginx/access.log | wc -l
# 看 p99(粗略)
awk '{print $NF}' /var/log/nginx/access.log | sort -n | tail -n 1
# 看响应时间分布
awk '{print $NF}' /var/log/nginx/access.log | sort -n | awk 'BEGIN{n=0} {a[n++]=$1} END {print "p50:", a[int(n*0.5)], "p95:", a[int(n*0.95)], "p99:", a[int(n*0.99)]}'
# 看 TOP 慢请求
awk '$NF > 1' /var/log/nginx/access.log | sort -k $NF -n -r | head -20
# 看最热 URI
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 看 IP 维度
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
8.6 ELK / Loki 集成
如果日志量大,推荐推到 ELK 或 Loki:
- ELK(Elasticsearch + Logstash + Kibana):成熟方案,适合大日志量。
- Loki + Grafana:轻量,云原生友好。
- Vector / Fluentd:日志收集 agent。
Nginx 推日志到 Logstash(filebeat 配置):
# /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
paths:
- /var/log/nginx/access.log
- /var/log/nginx/error.log
fields:
type: nginx
output.logstash:
hosts: ["logstash:5044"]
8.7 监控告警的“基线告警”
不要硬编码“必须 QPS > 10000”,而是要写基线告警:
# 当前 QPS 比 7 天前同时段 QPS 跌 30% 触发
(
rate(nginx_http_requests_total[5m])
<
rate(nginx_http_requests_total[5m] offset 7d) * 0.7
)
这种告警能发现业务回归,而不是只看绝对值。
九、排查路径
调优后性能突然变差,按下面路径排查:
- 确认变更:git diff / 部署记录,最近一次改动是什么。
- 看 stub_status:连接数是否爆、读写是否卡。
- 看 access log 5xx:上游还是 Nginx 自身。
- 看 error log:配置错、上游不可达、磁盘满。
- 看系统指标:CPU / 内存 / I/O / 端口。
- 看上游应用:是不是上游慢了。
- 回滚:对照配置管理回滚,看是否能恢复。
十、风险提醒
下面这些是高风险动作,执行前必须有备份和回滚。
10.1 nginx -s reload
- 风险:配置有语法错误,reload 失败,新连接无法接收,旧连接可能断。
- 替代:
nginx -t 先检查,nginx -T 看完整配置。
10.2 nginx -s stop
- 风险:立即关闭,正在处理的请求被中断,客户端报 502。
- 替代:
nginx -s quit 优雅退出;先停 80/443,再 quit。
10.3 修改 sysctl
- 风险:参数错误可能让系统无法访问网络。
- 替代:保留 ssh 备用通道,只改单个参数,改完立刻测。
10.4 修改 Worker 数量
- 风险:Worker 太多会导致 CPU 上下文切换;太少则不能充分利用多核。
- 替代:从
auto 开始,逐步调整,压测对比。
10.5 开启 proxy_cache
- 风险:缓存错会“用户看到旧内容”或“缓存命中率被错算”。
- 替代:在测试环境开,观察一段时间,确认业务可接受。
10.6 iptables 改限流
- 风险:可能锁死自己。
- 替代:用
limit_req / limit_conn,不直接 iptables。
10.7 大文件 grep / awk
- 风险:在 100G 的 access log 上跑
awk,容易引发 IO 风暴。
- 替代:用
pv 限制,或者用专门的日志系统(ELK / Loki)。
10.8 truncate /var/log/nginx/access.log
- 风险:进程还在写,文件变 0 字节但继续写。
- 替代:用
logrotate,postrotate 发 nginx -s reopen。
十一、验证方式
调优后必须用“可观察的指标”验证。
11.1 压测验证
# 同基线一样的 wrk
wrk -t8 -c200 -d60s --latency http://10.0.0.10:8080/
# 对比:
# Before: QPS=4800, p99=60ms
# After: QPS=9200, p99=22ms
11.2 真实业务验证
- 业务接口 curl:
curl -w "time_total=%{time_total}\n" http://10.0.0.10/api/health。
- 业务主链路:带上 token 的实际业务接口。
11.3 监控验证
- Prometheus 显示 Unchanged 或 Recovered。
- 告警:不再触发。
11.4 错误率验证
# 看 5xx 比例
awk '$9 ~ /^5/' /var/log/nginx/access.log | wc -l
# 与总请求数对比
wc -l /var/log/nginx/access.log
# 错误率 = 5xx / 总数
11.5 时间窗口观察
- 5 分钟:确认没有副作用。
- 15 分钟:确认稳态。
- 60 分钟:确认长期稳定。
十二、回滚方案
每个改动都要有回滚。
12.1 配置回滚
# 备份
cp /etc/nginx/nginx.conf /tmp/nginx.conf.bak-$(date +%Y%m%d-%H%M%S)
# 回滚
cp /tmp/nginx.conf.bak-* /etc/nginx/nginx.conf
nginx -t && nginx -s reload
12.2 sysctl 回滚
# 备份
sysctl -a > /tmp/sysctl.before-$(date +%Y%m%d-%H%M%S).txt
# 回滚
# 找到错误参数,改回
sysctl -w net.core.somaxconn=128
12.3 limits 回滚
# 备份
cp /etc/security/limits.d/90-nginx.conf /tmp/limits.bak
# 回滚
cp /tmp/limits.bak /etc/security/limits.d/90-nginx.conf
# 重新登录
12.4 缓存清理
# 缓存目录出错,清空(注意:会让缓存命中率掉到 0)
rm -rf /var/cache/nginx/proxy/*
# 或者更稳
mv /var/cache/nginx/proxy /var/cache/nginx/proxy.bak
mkdir /var/cache/nginx/proxy
chown -R nginx:nginx /var/cache/nginx/proxy
12.5 完整回滚剧本
- nginx -s reload 失败 → 用
nginx -c /tmp/nginx.conf.bak 启动。
- sysctl 错误 → 紧急重启,前提是机器有控制台。
- limits 错误 → 重新登录即可。
- 缓存错误 → 删除缓存目录,reload。
十三、生产环境注意事项
13.1 灰度
- 任何改动先在 1 个 Worker / 1 个实例上试。
- 用
try_files / error_page 切流量。
- 持续观察 5 分钟、15 分钟、1 小时再继续。
13.2 监控先行
- 调优前确认有 stub_status / exporter 接入监控。
- 没有监控的调优是“裸奔”。
13.3 双人复核
- nginx.conf 改动至少两人过。
- 重要变更走变更流程。
13.4 备份
- 改动前
cp 一份当前 nginx.conf 到 /tmp/。
- 重要变更保存到 git。
13.5 文档化
- 改动写入 CHANGELOG。
- 配置变更在 wiki / runbook 同步更新。
13.6 不要在生产首次开启
- proxy_cache / gzip / keepalive 在测试环境充分验证再上生产。
- 涉及多上游的 keepalive 先在一个上游试。
13.7 关注运行时信号
nginx -s reload 是 HUP,worker 优雅重启。
nginx -s quit 是 QUIT,master 优雅退出。
nginx -s reopen 是 USR1,重开日志文件,logrotate 用。
kill -USR2:升级二进制,平滑升级。
kill -WINCH:优雅关闭 worker(配合 USR2 升级)。
13.8 重要指标不要硬编码
- 阈值要结合业务调整,不能写死“必须 QPS > 10000”。
13.9 配置管理
- nginx.conf 纳入 git。
- 大集群用 Ansible / Salt / Puppet 统一管理。
- 用 confd / etcd / consul-template 实现动态配置(高阶)。
13.10 容量规划
- 业务量增长 50%,Nginx 的 CPU 是否够?fd 是否够?Worker 是否够?
- 提前 6 个月规划扩容,不要等“双 11 前一天”才升级。
- 容量测试:每年至少一次“业务峰值的 1.5 倍”压测。
13.11 团队知识沉淀
- 调优经验写进 wiki,新人可以照做。
- 失败案例写进复盘文档。
- 团队内每月一次“性能主题分享”,互相学习。
附录 A:一页纸调优清单
□ 1. 基线压测(wrk -t8 -c200 -d60s)
□ 2. 系统层
□ sysctl -p /etc/sysctl.d/99-nginx-tuning.conf
□ /etc/security/limits.d/90-nginx.conf
□ 3. Nginx 主配置
□ worker_processes auto
□ worker_rlimit_nofile 1048576
□ events { worker_connections 65535; multi_accept on; use epoll; accept_mutex off; }
□ 4. HTTP 层
□ sendfile on; tcp_nopush on; tcp_nodelay on
□ keepalive_timeout 65; keepalive_requests 1000
□ 5. 上游层
□ upstream { keepalive 64; }
□ proxy_http_version 1.1; proxy_set_header Connection "";
□ 6. 压缩
□ gzip on; gzip_min_length 1024; gzip_comp_level 5
□ 7. 缓存(可选,业务可接受)
□ proxy_cache_path + proxy_cache
□ 8. 日志
□ access_log buffer=32k flush=5s
□ 9. 限流
□ limit_req zone=req_per_ip burst=20 nodelay
□ 10. 验证
□ wrk 同样压测,看 QPS / p99 / 错误率
□ Prometheus 指标确认
□ 业务接口 curl 验证
□ 11. 固化
□ git 提交
□ 配置管理更新
□ 监控告警补全
附录 B:常见问题速查
B.1 502 Bad Gateway
- 上游不可达
nginx -t 看配置
curl http://upstream:port 看上游是否在听
B.2 504 Gateway Timeout
- 上游响应慢
- 调大
proxy_read_timeout
- 看上游应用是否在 GC / 慢 SQL
B.3 413 Request Entity Too Large
client_max_body_size 不够
- 调大
B.4 499 Client Closed Request
B.5 504 with upstream prematurely closed connection
B.6 内存持续上涨
- Worker 内存泄漏
- 升级 Nginx 版本
- 看模块是否有泄漏
B.7 CPU 100% 但 QPS 不高
- 事件循环打满
- 调
worker_processes / worker_connections
- 看是不是 upstream keepalive 没开
B.8 502 频繁但 access log 正常
- 上游有缓存或 sticky session 问题
- 看 upstream 块配置
附录 C:Nginx 编译选项参考
# 装依赖
apt install -y libpcre3 libpcre3-dev libssl-dev zlib1g-dev libxml2-dev libxslt1-dev libgd-dev libgeoip-dev libgoogle-perftools-dev
# 下载源码
cd /usr/local/src
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# 编译选项
./configure \
--prefix=/etc/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 \
--with-http_ssl_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-http_xslt_module=dynamic \
--with-http_image_filter_module=dynamic \
--with-http_geoip_module=dynamic \
--with-http_perl_module=dynamic \
--with-threads \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--with-stream_geoip_module=dynamic \
--with-stream_ssl_preread_module \
--with-google_perftools_module \
--with-debug
make
make install
注意:Ubuntu / Debian / RHEL 的发行版 Nginx 已预编译,多数情况不需要自编译;只有需要第三方模块(VTS、ModSecurity)时才需要自编译。
附录 D:给资深工程师的几条“非主流”建议
下面是我在生产环境用过的几个“非主流但有用”的技巧:
- 压测脚本要“去 error page”:压测时不要压有 502/504 处理的 URI,否则测出来的是 Nginx 抗 5xx 的能力,不是真实业务。
keepalive_requests 是个“安全阀”:防止单个 TCP 连接被占用太久。如果客户端有 bug 不关连接,这个参数会让连接定期强制关闭。
- 不要在 nginx.conf 里嵌密码 / 密钥:即使 ACL 严格,也不要在配置里硬编码。用环境变量 + secret 引入。
- 大文件上传场景,把
client_body_buffer_size 调小,搭配 client_body_temp_path 把临时文件放到独立盘,避免上传占满主盘 IO。
mirror 指令做流量镜像:把生产流量复制一份到测试环境做测试;这个能力 Web 服务器能做,但应用层做起来复杂。
vts 模块(第三方):可以看每个 location 的请求数 / 状态码 / 响应时间分布,调优时帮助很大。
- Lua + OpenResty 做复杂业务逻辑:Lua 比直接 Nginx 配置灵活,适合“限流、AB 测试、灰度”等场景。Nginx 官方不写 Lua,要用 OpenResty。
- Nginx 的
try_files 比 if -e $uri 安全:if is evil,能不用就不用。
map 比正则更高效:能在 server 块里用 map 做条件判断,比 location 块里用 if 高效。
- 整体留 20% 性能余量:不要把机器压到 100%,业务增长 20% 还能扛,不扩容都能再撑一个季度。
附录 E:调优复盘的“五个一”
每做完一次调优,写一份“调优复盘”,模板如下:
- 一基线:压测前 QPS / p99 / 错误率 / CPU / 内存是多少?
- 二改动:这次具体改了哪几个文件、哪几个参数?
- 三结果:压测后 QPS / p99 / 错误率是多少?错误率是否仍然 < 0.1%?
- 四副作用:有没有引入新问题?如果有,怎么修?
- 五固化:这次改动沉淀到了 git / 文档 / 监控 / runbook 里了吗?
这套“五个一”模板贴在 wiki,每次复盘照着写,30 分钟就能产出一份有信息量的复盘。
附录 F:不同业务场景的“调优配方”
不同业务对 Nginx 的关注点不同,下面给 3 个典型场景的调优配方,便于快速复用。
F.1 场景 1:API 网关(高并发、短请求)
worker_processes auto;
worker_rlimit_nofile 1048576;
events {
worker_connections 65535;
multi_accept on;
use epoll;
accept_mutex off;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
# 限流(API 网关必开)
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
# 后端 buffer(短请求,buffer 不要太大)
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 16k;
# 上游 keepalive(API 网关必开)
upstream backend {
server 10.0.0.11:8080;
keepalive 64;
}
server {
listen 80;
location / {
limit_req zone=api burst=50 nodelay;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
F.2 场景 2:静态 CDN(高吞吐、大文件)
worker_processes auto;
events {
worker_connections 4096; # 静态 CDN 不需要 6 万连接
multi_accept on;
}
http {
sendfile on; # 静态 CDN sendfile 必开
tcp_nopush on; # 包合并
tcp_nodelay off; # 大文件不要 nodelay
keepalive_timeout 30;
keepalive_requests 100;
# 大文件优化
output_buffers 8 32k;
postpone_output 1460;
open_file_cache max=10000 inactive=30s;
server {
listen 80;
root /var/www/static;
location / {
try_files $uri $uri/ =404;
}
location ~* \.(jpg|png|gif)$ {
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
}
F.3 场景 3:WebSocket 网关(长连接)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
注意:WebSocket 场景的 worker_connections 要相应调整,因为 1 个 WebSocket 连接占用 1 个 fd,1 万个 WebSocket 就是 1 万个 fd。
附录 G:Nginx 性能调优的“4 类反模式”
最后再点 4 类“看似在优化、实际在拖后腿”的反模式:
- 把所有的 if 都去掉:
if is evil 在 location 块内比较严重,在 server 块内相对可控。新手会把所有 if 替换成 map / try_files,但有时 if 反而更直观。判断标准:看是否引起 rewrite 死循环。
- 配置里加大量注释:
# 这里是开启 gzip 之类的注释,会让 reload 时解析变慢。配置要简洁,文档放 wiki。
- 多个 server 块混合:不同业务混在同一个 nginx.conf,定位问题难。按业务拆分 conf.d/。
- 不开监控就调优:没监控的调优,效果“看不见”,出问题“看不见”,做完不知道好不好。先 stub_status / exporter,再调优。
最后再强调一次:本文的“翻倍”不是某一项配置的功劳,是 4 层 + 12 步的连贯执行。读者照着做,可能效果比 2 倍好,也可能不到 2 倍,关键是看业务。没有银弹,只有工程化。
十四、总结
Nginx 性能调优翻倍的关键不是某个“神奇参数”,而是 4 层 + 12 步的连贯执行:
- 基线压测——没基线的调优是耍流氓。
- 系统层——内核参数、fd、端口。
- 主配置——Worker 数量、events、sendfile。
- HTTP 层——Keepalive、gzip、缓存。
- 上游层——upstream keepalive、buffer、proxy 调优。
- 日志 / 监控 / 限流——让调优后的状态可观察、可保护。
- 固化——纳入 git / 配置管理 / CI。
- 回滚——任何改动都有回滚预案。
实际做下来,真正起作用的 6-8 个改动是:
worker_processes auto
worker_rlimit_nofile 1048576
events.worker_connections 65535
http.sendfile on; tcp_nopush on; tcp_nodelay on
http.keepalive_timeout 65
upstream.keepalive 64 + proxy_http_version 1.1 + Connection ""
net.core.somaxconn = 65535
fs.file-max = 2097152
但只改这 8 项,不一定能翻倍——翻倍需要“4 层叠加”才能触发效应。如果你照着做,发现 QPS 提升不到 50%,那大概率是上游应用拖后腿,不在 Nginx 范围。
调优完成后,要持续监控、持续灰度、持续准备回滚,不要把“翻倍”作为最终目标,而要把“业务稳定”作为目标。QPS 翻倍但错误率涨 1%,得不偿失。
祝你在自己的生产环境里,能稳定地、不出事故地做完这次调优。