适用场景
网站遭遇单 IP 高并发刷量、图片与静态资源被下载工具疯狂抓取、API 接口被单点连接占满 worker,这类问题的共同特征是单个来源 IP 同时保持大量连接。limit_conn 是 Nginx 内置的并发连接限制模块,与 limit_req(限流)配合使用,可以精确控制每个 IP、每个虚拟主机同一时刻能建立的连接数量。本文介绍如何配置 Nginx limit_conn 并发限制,用于防御单 IP 高并发连接滥用。
前置条件
- 已安装 Nginx 1.12 及以上版本(limit_conn 为核心内置模块,无需额外编译)
- 拥有服务器 root 或 sudo 权限,可修改主配置文件
- 明确业务正常并发水位:先观察 access.log 确认单个正常用户同时打开的连接数上限
- 若后端为 PHP-FPM,建议同步了解 fastcgi 连接行为,避免误伤长连接场景
原理说明
limit_conn 模块基于共享内存中的键值计数器工作。使用 limit_conn_zone 指令在共享内存中划分一块区域,以指定变量(通常是客户端 IP $binary_remote_addr)作为键;每当新连接进入时计数器加一,连接关闭时减一。当同一键的活跃连接数超过 limit_conn 指令设定的阈值时,Nginx 直接返回 503 Service Unavailable(可通过 limit_conn_status 自定义)。
关键区别:limit_req 限制的是请求速率(每秒多少个请求),limit_conn 限制的是并发连接数(同一时刻挂起多少条连接)。下载工具、爬虫的典型特征是同时发起 10 条以上连接持续拉取大文件,这类行为速率不一定超标,但并发连接数远超正常浏览器(浏览器对同一域名一般并发 6 条连接),因此二者必须配合使用。
操作步骤
第一步:定义共享内存区域
在 http 块中定义连接计数区域,perip 为区域名,10m 内存约可记录 16 万个 IP 的连接状态:
http {
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
}
注意键必须使用 $binary_remote_addr 而非 $remote_addr,前者为 4 字节二进制形式,比字符串形式节省内存且计数更精确。
第二步:在 server 或 location 中应用限制
server {
listen 80;
server_name example.com;
# 全站:单 IP 同时最多 20 条连接
limit_conn perip 20;
# 整个虚拟主机:所有 IP 合计最多 500 条连接
limit_conn perserver 500;
# 超限返回 429,便于前端与监控识别
limit_conn_status 429;
# 静态下载目录执行更严格的限制
location /download/ {
limit_conn perip 2;
limit_rate 512k;
}
# 静态资源放宽到正常浏览器水平
location ~* \.(css|js|png|jpg|webp)$ {
limit_conn perip 10;
}
}
location 级别的 limit_conn 会继承并叠加 server 级别的限制,即 /download/ 目录下的请求同时受 perip 20 和 perip 2 两条规则约束,实际生效的是更严格的 2 条。
第三步:配置限流配合规则(可选)
并发限制挡不住单连接内的暴力刷请求,建议叠加 limit_req:
http {
limit_req_zone $binary_remote_addr zone=reqperip:10m rate=10r/s;
server {
limit_req zone=reqperip burst=20 nodelay;
}
}
第四步:重载配置
nginx -t && systemctl reload nginx
配置验证
使用 ab 或 curl 模拟并发连接,观察超限是否返回 429:
# 模拟 30 并发、共 60 个请求,观察 Non-2xx responses 数量
ab -n 60 -c 30 http://example.com/download/test.zip
# 或用多进程 curl 保持连接观察
for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" http://example.com/download/test.zip & done | sort | uniq -c
预期结果:约 2 条请求返回 200,其余返回 429。同时查看错误日志确认触发记录:
tail -f /var/log/nginx/error.log
# 输出类似:limiting connections by zone "perip", client: 1.2.3.4, ...
常见问题
FAQ 1:正常用户被误限,页面资源加载不全
现代浏览器对同一域名可并发 6 条连接,若页面聚合了多个第三方域名或使用 HTTP/2 之外的协议,20 条以内的阈值一般安全。误限排查方法:检查 error.log 中被限制的 URI,若是正常页面资源,则调大对应 location 的 limit_conn 或将其排除在限制外。注意 HTTP/2 场景下浏览器复用单条 TCP 连接,limit_conn 计数的是 TCP 连接,HTTP/2 用户天然受影响更小;若误限集中出现在 HTTP/2 用户上,检查是否开启了 http2 on 且连接被计数多次。
FAQ 2:CDN 或反代场景下所有请求被判定为同一 IP
若站点前有 CDN、负载均衡或 Nginx 反代,$binary_remote_addr 取到的是代理节点 IP,全站用户共享同一个计数器,会导致大面积 429。解决方法:使用 CDN 传递的真实 IP 头作为键:
limit_conn_zone $http_x_forwarded_for zone=perip:10m;
更规范的做法是配合 ngx_http_realip_module,设置 set_real_ip_from 信任 CDN 网段后继续使用 $binary_remote_addr,这样日志与限制使用的都是真实客户端 IP。
FAQ 3:503 与 429 应该返回哪个
Nginx 默认返回 503。429 Too Many Requests 语义更准确,且便于 CDN 和客户端按标准行为做退避重试,建议显式配置 limit_conn_status 429。
总结
limit_conn 解决的是并发连接层面的资源占用问题,与 limit_req 的速率限制、limit_rate 的带宽限制构成 Nginx 三层流量防护体系。配置要点:以 $binary_remote_addr 为键、先观察正常水位再定阈值、静态下载目录单独收紧、CDN 场景务必处理真实 IP。阈值并非越小越好,建议从单 IP 20 条起步,结合 error.log 的触发记录逐步收敛。