「我的服务器 4 核 8G,Nginx 该配几个 worker?」——这是被问得最多、也被答得最糊的问题。网上流传的答案从「worker_processes 1; 最省内存」到「配 64,越多越快」都有,彼此矛盾却都言之凿凿。更麻烦的是,很多人调完 worker_processes 和 worker_connections 之后,发现页面该慢还是慢,于是断定「这些参数没用」。
问题不在参数,在于没人把这两个数字背后的容量模型讲清楚。本文就把模型拆开:worker_connections 到底数的是什么、为什么它不等于最大并发用户数、worker_processes 什么时候该等于核数什么时候不该、以及如何用压测和二进制的 worker_connections are not enough 告警反推真实容量。
先纠一个常见错误:worker_connections 不是并发用户数
Nginx 文档里对 worker_connections 的描述是「设置单个 worker 进程允许同时打开的最大连接数」。这里的关键词是连接,不是用户,也不是请求。
一个浏览器访问一个页面,通常要建立 1 到 6 个 TCP 连接(HTTP/1.1 每域名并行上限),如果页面走 HTTPS 且有 HTTP/2,通常只有 1 个连接承载几十个请求。但 Nginx 这一侧还要额外算上反向代理时的上游连接。
这就是最容易被忽略的一点:当 Nginx 是反向代理时,一个用户请求会同时占用两个连接:
- 一个客户端连接(浏览器 ↔ Nginx)
- 一个上游连接(Nginx ↔ PHP-FPM / Node / 后端服务)
所以粗略的换算关系是:
单个 worker 能承载的并发请求数 ≈ worker_connections / 2 (反向代理场景)
单个 worker 能承载的并发请求数 ≈ worker_connections / 1 (纯静态文件场景)如果还启用了 HTTP/2 并同时保持 HTTP/1.1 兼容、或者做了 proxy_cache 之外还要回源,实际系数会更偏向 2。厂值 worker_connections 512; 意味着反代场景下单个 worker 只够约 256 个并发请求。这就是为什么很多「按默认配置跑得好好的」小站,一上活动流量就冒出大量 502 和 worker_connections are not enough。
容量公式:从并发目标反推算参数
不要先问「配多少」,先问「我要扛多少并发」。设:
- C = 目标同时在线请求数(connection-level concurrency,不是 UV)
- k = 每请求的连接系数,纯静态 1,反代 2
- N = worker_processes
- W = worker_connections
要求 N × W ≥ C × k,并且留 20%~30% 余量。反推 W:
W ≥ ceil(C × k × 1.25 / N)举个具体例子。一台 4 核 8G 的 VPS,跑 Nginx + PHP-FPM 的 WordPress 站,目标是扛住 800 个并发请求:
C = 800, k = 2, N = 4
W ≥ ceil(800 × 2 × 1.25 / 4) = ceil(500) = 500
取整到 1024(便于记忆,且留足余量)于是 worker_processes 4; worker_connections 1024; 就够。理论上限是 4 × 1024 / 2 = 2048 并发请求,是目标的 2.5 倍,安全。
反过来看一个常见的错误配置:8 核机器配 worker_connections 512,反代场景理论上限 8 × 512 / 2 = 2048 并发。看起来不少,但这是绝对上限,一旦触达,Nginx 会直接拒绝新连接(返回 503 或直接断开),而不是排队。所以容量规划必须留余量,不能把理论上限当实际能力。
worker_processes:auto 还是手工指定
worker_processes auto; 是现在绝大多数场景的正确答案,Nginx 会自动读取 CPU 核数(通过 sysconf(_SC_NPROCESSORS_ONLN))。它等价于「让 Nginx 自己数核」。
但有几个场景不该用 auto:
场景一:容器里共享宿主 CPU
Docker / Kubernetes 里,auto 读到的可能是宿主机的全部核数,而不是容器的 CPU quota。一个 --cpus=1 的容器跑在一台 16 核宿主机上,auto 会起 16 个 worker,每个都在抢那 1 核的时间片,上下文切换成本反而上升。正确做法是显式写数字,或者让 Nginx 读不到宿主拓扑:
# 容器 CPU 限制为 1 核时,显式指定
worker_processes 1;
# 若使用 cgroup v2 且 Nginx 版本较新(1.23+),auto 已能正确读取 cpu.max,
# 但仍建议核对:
nproc # 容器内看到的核数
cat /sys/fs/cgroup/cpu.max # cgroup v2: "100000 100000" 表示 1 核场景二:和 PHP-FPM 抢同一批核
这是个人站长最常踩的坑。4 核机器,Nginx 配 worker_processes auto(4 个),PHP-FPM 配 pm.max_children = 20,MySQL 又是单进程多线程。结果 4 个 Nginx worker 加 20 个 PHP 子进程一起抢 4 个核,load average 冲到 20 以上,每个请求的排队时间比处理时间还长。
这时正确做法不是「把 Nginx worker 减到 1」,而是认识到 Nginx 是事件驱动的、几乎不吃 CPU,真正吃核的是 PHP-FPM 子进程。所以调整方向应该是压缩 pm.max_children,让 PHP 的并发数与核数匹配,而不是牺牲 Nginx 的并发能力。
一个实用的经验区间(4 核 8G 内容站):
| 组件 | 参数 | 建议值 | 理由 |
|---|---|---|---|
| Nginx | worker_processes | 4 | 等于核数,事件驱动开销极小 |
| Nginx | worker_connections | 2048 | 反代场景上限 4096 并发 |
| PHP-FPM | pm.max_children | 8~12 | 按单进程内存约 60MB 计算,留出系统余量 |
| PHP-FPM | pm | static 或 dynamic | 内存够用优先 static,避免频繁 fork |
| MySQL | innodb_buffer_pool_size | 2~3G | 剩余内存的 50%~60% |
注意 worker_connections 写 2048 并不会真的占 2048 份内存——它只是一个上限计数器,每连接的实际内存开销在几 KB 到几十 KB 之间(受 client_header_buffer_size、large_client_header_buffers 等影响),连接空闲时才真正分配读写缓冲。所以调大 worker_connections 本身几乎无成本,真正需要节制的是 PHP-FPM 子进程数。
worker_rlimit_nofile:被忘记的另一半
调大了 worker_connections 却依然报 worker_connections are not enough,或者 error.log 里出现 Too many open files,那问题多半不在 Nginx 配置,而在文件描述符上限。
Nginx 每个连接至少要占用 1 个文件描述符,反代场景是 2 个。所以 worker_connections 2048 实际需要至少 4096 个 FD,加上监听套接字、日志文件、缓存文件等,要 5000 以上。worker_rlimit_nofile 是 Nginx 主动去调用 setrlimit() 提升自身进程上限的指令:
worker_rlimit_nofile 65535;但它不能超过系统级上限。完整链条是三层:
# 1) 系统级
sysctl fs.file-max # 全局上限,通常几十万,一般不是瓶颈
sysctl fs.nr_open # 单进程硬上限
# 2) systemd 服务级(Nginx 由 systemd 启动时,这个才生效)
systemctl show nginx -p LimitNOFILE
# 若输出 1024,需要在 override 里提高:
mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf <<'EOF'
[Service]
LimitNOFILE=65535
EOF
systemctl daemon-reload && systemctl restart nginx
# 3) Nginx 进程级
# worker_rlimit_nofile 65535; 写在主配置顶层
# 验证:看实际进程的 limits
grep "Max open files" /proc/$(cat /run/nginx.pid)/limits这三层任意一层卡住,都会导致调大 worker_connections 无效。systemd 这一层是最常见的漏网之鱼,因为很多人改完 /etc/security/limits.conf 就去测试,却发现对 systemd 管理的服务完全不起作用——那个文件只对 PAM 登录会话生效。
怎么确认「已经到瓶颈了」
调参数之前先确认瓶颈在哪,否则就是瞎调。三个观察点:
观察点一:error.log 里的连接拒绝
Nginx 在 worker_connections 耗尽时会写:
2026/10/17 09:12:44 [alert] 12345#0: *998877
worker_connections are not enough while connecting to upstream,
client: 203.0.113.5, server: example.com, request: "GET / HTTP/1.1"看到 [alert] 级别加上这句,就是百分之百的容量不足,直接调大 worker_connections。反过来,如果没有这条而用户仍然抱怨慢,那瓶颈不在连接数——去查 PHP-FPM 的 listen queue 或 MySQL 慢查询。
观察点二:当前活跃连接数
stub_status 模块给的状态页是最直接的工具:
# nginx.conf 中
location = /nginx_status {
stub_status;
access_log off;
allow 127.0.0.1;
deny all;
}
# 查看
curl -s http://127.0.0.1/nginx_status
# Active connections: 291
# server accepts handled requests
# 16630948 16630948 31070465
# Reading: 6 Writing: 179 Waiting: 106关键看 Active connections 和 Waiting。Waiting 是已建立但无请求的 keepalive 连接,它同样占用 worker_connections 名额。这就是纯静态站点也容易被 keepalive 淹没的原因:默认 keepalive_timeout 75s;,一个用户开着标签页挂一分钟就占一个名额。对静态站可以把超时降到 15~20 秒,显著降低空闲连接占用。
观察点三:worker 之间的负载是否均衡
如果多个 worker 中有一个 CPU 跑满、其余空闲,说明 accept_mutex 或连接分配有问题。现代 Nginx 默认 accept_mutex off;,靠内核的 SO_REUSEPORT 分发,一般不需要动。但在某些虚拟化环境(尤其是老内核或 KVM 上网络中断绑定不佳时)会出现明显偏斜。检查方法:
# 看每个 worker 的 CPU 时间是否相近
top -H -p $(pgrep -d, -o nginx) | head -20
# 或者直接看所有 nginx 进程
ps -o pid,pcpu,etime,cmd -C nginx若确实偏斜严重且内核较老,可以显式开启互斥:accept_mutex on;,代价是有一点点唤醒延迟。
压测验证:别用 ab,用 wrk
调完参数一定要验证。ab 是单线程的,压不满多核,测出来的数字会严重低估真实能力。用 wrk,它支持多线程和 keepalive,更接近真实浏览器行为:
# 4 线程、100 并发、持续 30 秒、保持 keepalive
wrk -t4 -c100 -d30s --latency https://example.com/
# 输出解读
# Requests/sec: 1823.45 <- 实际 QPS
# Latency P50 48.20ms
# P99 210.55ms <- 尾部延迟,比平均值重要得多压测的核心不是看最大 QPS 有多漂亮,而是找拐点:并发从 50 加到 100 时 QPS 线性上升,加到 200 时 QPS 不升反降、P99 暴涨,说明 200 就是这台机器的饱和点。此后所有优化(缓存、静态化、数据库索引)都是在把这个拐点往右推。
还有一个必须在压测中做的动作:同时开一个新终端跑 vmstat 1 和 ss -s,观察 r 列(运行队列)、si/so(swap 活动)、以及 TCP 连接总数。如果 si/so 非零,说明已经在用 swap,此时任何 Nginx 参数调优都是徒劳——先把内存问题解决。
小结与推荐配置
把上面所有内容收敛成一份可以直接用的模板(4 核 8G,Nginx + PHP-FPM 反代,目标 800 并发):
worker_processes 4;
worker_rlimit_nofile 65535;
events {
worker_connections 2048;
multi_accept on;
use epoll;
}
http {
keepalive_timeout 20s;
keepalive_requests 200;
# ... 其余配置
}配套的 systemd override 记得加上 LimitNOFILE=65535,否则前两行是白写的。
最后说一句方法论上的话:worker_processes 和 worker_connections 是天花板参数,不是性能参数。调大它们不会让站点变快,只会让站点在流量高峰时不那么容易崩。真正决定速度的是缓存命中率、PHP 执行时间、数据库查询效率这些实打实的东西。搞混了这两类参数,就会出现「参数调到极致,页面还是三秒白屏」的困惑——那时候该看的是 TTFB 落在哪一段,而不是继续加 worker。