Nginx limit_conn 实战:单 IP 并发连接限制配置

适用场景

网站遭遇单 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 的触发记录逐步收敛。