一、引言:为什么需要限制"好"客户端?
在互联网世界,流量既是财富,也可能是灾难。一个设计良好的系统,不仅要能服务海量的正常用户,更要能抵御恶意的、异常的流量冲击。
Nginx 作为 Web 架构的流量入口,天然地承担着"守门人"的角色。通过对客户端施加合理的限制,我们可以:
- 防止资源耗尽:避免单个恶意 IP 占满所有连接或带宽。
- 抵御 DDoS/CC 攻击:有效缓解基于 HTTP 的洪水攻击。
- 保障服务公平性:确保正常用户的请求能得到及时响应。
- 控制爬虫行为:友好地限制搜索引擎或恶意爬虫的抓取频率。
💡 核心价值 :
掌握 Nginx 的客户端限制能力,是构建一个健壮、高可用 Web 服务不可或缺的安全基石!
二、三大核心限制策略:连接、请求与带宽
Nginx 主要通过三个内置模块来实现对客户端的精细化控制。
1. ngx_http_limit_conn_module - 并发连接数限制
- 作用 :限制来自同一个客户端(通常是 IP)同时建立的 TCP 连接数量。
- 适用场景:防止单个用户通过大量并发连接耗尽服务器的文件描述符或后端应用的工作线程。
- 核心指令 :
limit_conn_zone: 定义用于存储连接状态的共享内存区域。limit_conn: 在http,server, 或location块中应用限制规则。
2. ngx_http_limit_req_module - 请求速率限制
- 作用 :限制来自同一个客户端在单位时间内的请求数量(QPS)。
- 适用场景:防刷、防爬虫、防 CC 攻击,平滑突发流量。
- 工作原理 :基于漏桶算法(Leaky Bucket),以恒定速率处理请求。
- 核心指令 :
limit_req_zone: 定义用于存储请求状态的共享内存区域,并设置速率。limit_req: 应用限制规则,并可配置突发 (burst) 和是否延迟 (nodelay)。
3. limit_rate - 单连接带宽限制
- 作用 :限制单个连接向客户端传输数据的最大速率。
- 适用场景:防止大文件下载占满出口带宽,影响其他用户;实现简单的 QoS (服务质量) 策略。
- 核心指令 :
limit_rate: 设置速率(字节/秒)。
三、实战配置详解
1. 限制并发连接数
http {
# 定义一个名为 "perip" 的共享内存区域,大小为 10MB,
# 用于存储客户端 IP ($binary_remote_addr) 的连接计数。
limit_conn_zone $binary_remote_addr zone=perip:10m;
server {
listen 80;
server_name example.com;
location / {
# 限制每个 IP 最多只能有 20 个并发连接
limit_conn perip 20;
# 可选:自定义超过限制时返回的状态码
limit_conn_status 503;
proxy_pass http://backend;
}
}
}
$binary_remote_addr: 使用二进制格式存储 IP 地址,比字符串格式更节省内存。zone=perip:10m: 共享内存区域名必须唯一,10MB 内存大约可以存储 16 万个 IP 地址的状态。
2. 限制请求速率(带突发)
http {
# 定义一个名为 "one" 的共享内存区域,大小为 10MB,
# 限制每个 IP 的请求速率为每秒 10 个 (10r/s)。
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
location /api/ {
# 应用限制规则
# burst=20: 允许突发最多 20 个额外请求进入"缓冲队列"
# nodelay: 不延迟处理突发的请求,直接放行(否则会按速率匀速处理)
limit_req zone=one burst=20 nodelay;
proxy_pass http://api_backend;
}
}
}
burst: 漏桶的容量。当请求速率超过rate时,多余的请求(不超过burst)会被暂时放入队列。nodelay: 如果不加此参数,突发的请求会被延迟处理,以保证平均速率不超过rate。加上后,突发请求会立即被处理,但一旦burst队列满,后续请求将被拒绝。
3. 限制单连接带宽
server {
location /download/ {
# 限制每个连接的下载速度为 128KB/s
limit_rate 128k;
# 可以结合内部变量实现动态限速
# 例如,对特定 User-Agent 限速
if ($http_user_agent ~* "BadBot") {
set $limit_rate 1k;
}
alias /path/to/files/;
}
}
limit_rate 128k:k或K表示千字节/秒,m或M表示兆字节/秒。
四、高级技巧与注意事项
1. 识别真实客户端 IP
如果 Nginx 前面还有 CDN 或代理(如 LVS, HAProxy),$remote_addr 获取到的是代理服务器的 IP,而非真实用户 IP。此时需要配合 real_ip 模块:
set_real_ip_from 192.168.1.0/24; # 可信代理的IP段
real_ip_header X-Forwarded-For; # 从该Header中获取真实IP
real_ip_recursive on; # 递归解析X-Forwarded-For
# 之后再使用 $realip_remote_addr 或重新赋值 $binary_remote_addr
map $realip_remote_addr $client_ip {
"" $remote_addr;
default $realip_remote_addr;
}
limit_conn_zone $client_ip zone=real_perip:10m;
2. 白名单机制
对于重要的合作伙伴或监控系统,我们通常需要将其加入白名单,豁免限制。可以结合 geo 模块实现:
geo $limited_ip {
default 1; # 默认所有IP都需要被限制
10.0.0.0/8 0; # 内网IP豁免
1.2.3.4 0; # 特定IP豁免
}
map $limited_ip $limit_key {
0 ""; # 豁免IP,映射为空字符串
1 $binary_remote_addr; # 需要限制的IP,映射为其IP
}
limit_req_zone $limit_key zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
}
3. 日志记录与监控
在 access_log 中记录被限制的请求,便于后续分析和告警。
log_format main_ext '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time" '
'limit_req_status=$limit_req_status'; # 记录限流状态
access_log /var/log/nginx/access.log main_ext;
五、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!