文章总结: 本文详细讲解Nginx限流实战,重点介绍使用limitreqzone、limitconnzone和limit_req三个核心指令防御CC攻击和突发流量。文章涵盖漏桶算法原理、burst/nodelay参数区别、key选择策略,并提供按IP限速、多级接口限流等可直接落地的配置示例和压测方法,帮助运维人员快速构建生产级限流防护体系。 综合评分: 85 文章分类: WEB安全,安全工具,实战经验,解决方案,安全运营
Nginx 限流实战:如何优雅地防御 CC 攻击和突发流量
点击关注 👉 点击关注 👉
马哥Linux运维
2026年6月28日 13:00 广东
在小说阅读器读本章
去阅读
Nginx 限流实战:如何优雅地防御 CC 攻击和突发流量
一、问题背景
Nginx 是绝大多数互联网公司的接入层首选,但它默认没有任何限流能力。一旦面对 CC 攻击、突发流量、爬虫狂扫,CPU 飙升、连接数打满、带宽耗尽几乎是必然的。
实际生产中常见的”被打挂”场景大致是这样:
- 凌晨 2 点告警群突然@全员:某个 URI 的 QPS 突增 100 倍
- 业务方做了个限时活动,10 万用户同时点按钮
- 黑产用脚本扫登录接口、撞验证码接口
- 竞争对手爬虫把首页和列表页全量抓一遍
- 某个 SDK 版本有 bug,客户端疯狂重试
这些场景下,Nginx 如果没有限流,最直接的后果就是:CPU 100% → worker 卡死 → upstream 503 → 全站雪崩。靠防火墙挡 IP 是事后补救,靠 WAF 是另一条产品线,而 Nginx 自带的限流模块在大多数中小规模场景下就够了。
Nginx 限流的核心能力来自三个指令:limit_req_zone、limit_conn_zone、limit_req。再配合 geo 模块做黑白名单、map 做请求识别、OpenResty 做复杂逻辑,就能搭建一套相对完整的接入层限流体系。
这一篇不讲 RFC 文档的抽象概念,直接站在运维视角,把限流配置、压测、监控、回滚讲清楚。
写作目标:
- 让初中级运维能够独立配置一套生产级限流
- 让读者在被 CC 攻击时 10 分钟内上线防护
- 让读者能够根据业务场景调优限流参数
- 给出可直接落地的配置、压测命令、监控告警
二、适用场景
- 接入层 CC 攻击防护
- 突发流量削峰(限时活动、秒杀、抢购)
- 爬虫与恶意请求拦截
- 单 IP / 单用户 / 单接口限速
- 接口级精细化限流(登录、注册、下单、支付)
- API 网关层限流
- 后端微服务限流
- CDN 回源限流
- 灰度发布期间限流
- 客户端 SDK 异常重试防护
- 数据库写操作限流
- 短信、邮件发送限流
- 验证码、Token 颁发限流
- 文件下载带宽限流
- 移动端弱网限流
- 跨机房流量整形
三、核心知识点
3.1 Nginx 限流三剑客
3.1.1 limit_req_zone
请求速率限流,按”在单位时间窗口内允许的请求数”控制。
语法:
nginx
limit_req_zone $key zone=name:size rate=rate;
-
$key:限流的 key,常用
$binary_remote_addr(按 IP)、$server_name(按域名)、自定义变量 -
zone:共享内存区域,
name:size格式 -
rate:限流速率,如
10r/s(每秒 10 个请求)、60r/m(每分钟 60 个)
底层算法:leaky bucket(漏桶),令牌按固定速率填充,请求消耗令牌,没有令牌就拒绝或排队。
3.1.2 limit_req
在具体的 location 或 server 中应用限流规则。
语法:
nginx
limit_req zone=name [burst=number] [nodelay | delay=number];
-
zone:对应 limit_req_zone 的名称
-
burst:允许的突发请求数,超过
rate的请求会放到队列 -
nodelay:超过 rate 的请求立即返回,不排队
-
delay:超过 burst 的请求延迟处理
注意:burst 不是”突发流量总容量”,而是”排队容量”。
3.1.3 limit_conn_zone
连接数限流,控制单个 key 的并发连接数。
语法:
nginx
limit_conn_zone $key zone=name:size;
nginx
limit_conn zone_name number;
- 控制同时活跃的连接数
- 适合限制长连接、慢请求
3.1.4 三个状态码指令
nginx
limit_req_status 429;
limit_conn_status 429;
- 默认 503,可以改成 429
- 客户端能识别 429 后做退避
3.1.5 日志级别
nginx
limit_req_log_level warn;
limit_conn_log_level warn;
- 控制限流事件的日志输出级别
- 默认
error,生产环境建议warn
3.2 漏桶算法
Nginx 的 limit_req 实现是漏桶算法:
令牌按 rate 速率匀速填充到桶里
桶容量 = burst
请求到达时消耗一个令牌
桶空则请求被拒绝(或排队)
举例:
rate=10r/s burst=20 nodelay- 桶里平时有 0 个令牌
- 突发 20 个请求全部通过
- 之后每个新请求要等 100ms 才有令牌
- 第 21 个以后的请求立即被拒绝
与令牌桶的区别:
- 漏桶:输出速率恒定(r),适合削峰
- 令牌桶:允许一定突发,适合允许抖动的场景
3.3 burst / nodelay / delay 的区别
| 参数 | 行为 | 适用场景 |
| — | — | — |
| 无 burst | 严格按 rate,超出立即拒绝 | 严格限流 |
| burst=N | 允许 N 个请求排队等待 | 允许抖动 |
| burst=N nodelay | N 个以内立即通过,之外立即拒绝 | 允许突增但不排队 |
| burst=N delay=M | 排 M 个以内立即通过,剩余排队 | 折中 |
举例说明:
nginx
# 配置 1:严格限流
limit_req zone=api_limit burst=0;
# 行为:超过 1r/s 立即 503
# 配置 2:允许 20 个突发
limit_req zone=api_limit burst=20;
# 行为:超过 1r/s 的请求先排队,排队满 20 个就 503
# 配置 3:允许 20 个突发,但不排队
limit_req zone=api_limit burst=20 nodelay;
# 行为:超过 1r/s 的请求,20 个以内立即通过,超过 20 个立即 503
# 配置 4:折中
limit_req zone=api_limit burst=20 delay=10;
# 行为:超过 1r/s 的请求,前 10 个立即通过,10-20 之间排队,20 之外 503
3.4 key 的选择
$binary_remote_addr 是最常用的 key(按 IP 限流),但实际场景可能更复杂:
| 场景 | key | 备注 |
| — | — | — |
| 按 IP | $binary_remote_addr | 最常用,节省内存 |
| 按 IP + URL | binary_remote_addr$request_uri | 防止单 IP 扫多个 URI |
| 按用户 | $http_authorization 中的用户 ID | 登录后用 |
| 按域名 | $server_name | 防止某个域名被打 |
| 按接口 | $request_uri | 防止单 URI 被刷 |
| 按 IP+UA | 自定义组合 | 防止代理 IP 轮换 |
注意:
- 共享内存区域大小与 key 数量相关
- 1MB 共享内存大约能存 16K 个 IP
- key 数量超出 zone 大小会按 LRU 淘汰
3.5 常见误区
误区一:限流配了就有用。
错。如果 rate 太大、burst 太大,限流根本起不到作用。要按真实业务量压测后调整。
误区二:限流不丢请求。
错。超过 rate 的请求必然被拒绝(除非 burst 排队)。要做好客户端的 429 重试和退避。
误区三:限流一次到位。
错。限流参数要按业务增长持续调整,突发流量是动态的。
误区四:所有接口都限流。
错。读多写少、缓存命中率高的接口,限流优先级低;写多、消耗资源的接口,限流优先级高。
误区五:限流能挡住所有攻击。
错。分布式攻击(不同 IP)需要配合 WAF、验证码、IP 信誉库。
四、整体排查与实施思路
4.1 限流实施的五个层次
1. 客户端限流(SDK 重试退避、按钮防抖)
2. 接入层限流(Nginx / CDN / WAF)
3. 应用层限流(Sentinel / Guava RateLimiter / 自研)
4. 中间件限流(Redis 令牌桶 / MQ 限流)
5. 数据库限流(连接池 / 慢 SQL 拦截)
每一层都要做。
4.2 限流参数的设计步骤
-
业务基线评估
:通过监控拿到正常时期的 QPS
-
峰值评估
:按业务量 + 3-5 倍冗余估算峰值
-
突发评估
:按 1.5-2 倍峰值估算突发
-
分接口评估
:登录、下单、查询接口分开评估
-
分用户评估
:VIP 和普通用户分开评估
4.3 限流的实施顺序
-
先评估
:用监控、日志拿到真实数据
-
再压测
:用
wrk/ab/vegeta压测 -
后配置
:按压测结果配 limit_req
-
要灰度
:先在部分节点上线
-
再观察
:看 429 比例、客户端影响
-
最后全量
:扩大到所有节点
4.4 限流异常的排查步骤
1. 429 比例突增 → 检查 limit_req 配置、burst 设置
2. 误伤正常用户 → 检查 key 选择、rate 是否过严
3. 上游 QPS 没降 → 检查 limit_req 是否真的应用
4. 客户端重试风暴 → 客户端要做指数退避
5. 内存溢出 → 检查 zone 大小是否足够
五、实战步骤
5.1 基础限流配置:按 IP 限速
第一步:在 http 块中定义限流 zone。
nginx
# /etc/nginx/nginx.conf
http {
# 按 IP 限流:每秒 10 个请求
limit_req_zone$binary_remote_addr zone=ip_limit:10m rate=10r/s;
# 限制连接数:每个 IP 最多 100 个连接
limit_conn_zone$binary_remote_addr zone=conn_limit:10m;
# 返回 429 而不是 503
limit_req_status429;
limit_conn_status429;
# 限流日志级别
limit_req_log_levelwarn;
limit_conn_log_levelwarn;
server {
listen80;
server_name api.example.com;
location / {
# 应用限流:允许 20 个突发,不排队
limit_req zone=ip_limit burst=20 nodelay;
limit_conn conn_limit 100;
proxy_pass http://backend;
}
}
}
第二步:测试配置并 reload。
bash
nginx -t
nginx -s reload
第三步:压测验证。
bash
# 用 ab 压测 60 秒 100 并发
ab -n 6000 -c 100 'http://api.example.com/api/test'
# 看响应码分布
# 大部分应该是 200,少量 429
5.2 多级限流:按接口分别限流
nginx
http {
# 登录接口:每 IP 每秒 1 个
limit_req_zone$binary_remote_addr zone=login_limit:10m rate=1r/s;
# 注册接口:每 IP 每秒 1 个
limit_req_zone$binary_remote_addr zone=register_limit:10m rate=1r/s;
# 查询接口:每 IP 每秒 50 个
limit_req_zone$binary_remote_addr zone=query_limit:10m rate=50r/s;
# 下单接口:每 IP 每秒 5 个
limit_req_zone$binary_remote_addr zone=order_limit:10m rate=5r/s;
# 全局兜底:每 IP 每秒 100 个
limit_req_zone$binary_remote_addr zone=global_limit:10m rate=100r/s;
server {
listen80;
server_name api.example.com;
# 登录接口
location /api/login {
limit_req zone=login_limit burst=5 nodelay;
proxy_pass http://backend;
}
# 注册接口
location /api/register {
limit_req zone=register_limit burst=3 nodelay;
proxy_pass http://backend;
}
# 查询接口
location /api/query {
limit_req zone=query_limit burst=100 nodelay;
proxy_pass http://backend;
}
# 下单接口
location /api/order {
limit_req zone=order_limit burst=10 nodelay;
proxy_pass http://backend;
}
# 默认:全局限流
location / {
limit_req zone=global_limit burst=200 nodelay;
proxy_pass http://backend;
}
}
}
5.3 多维度限流:按 IP + URI 限流
nginx
http {
# 按 IP + URI 限流:每个 IP 对每个 URI 每秒 5 个
limit_req_zone $binary_remote_addr$request_uri zone=ip_uri_limit:20m rate=5r/s;
server {
location / {
limit_req zone=ip_uri_limit burst=10 nodelay;
proxy_pass http://backend;
}
}
}
注意:
-
zone占用内存 = 16K × key 数量
-
IP+URI 组合会让 key 数量激增,zone 要相应调大
-
20MB 共享内存大约能存 320K 个 IP+URI 组合
5.4 限流 + 白名单(IP 不限流)
nginx
http {
# 定义白名单:内网和办公网不限流
geo$limit_ip {
default1; # 默认需要限流
10.0.0.0/8 0; # 内网不限流
172.16.0.0/12 0; # 办公网不限流
192.168.0.0/16 0; # 办公网不限流
}
# key 用空字符串表示不限流
limit_req_zone$limit_ip zone=ip_limit:10m rate=10r/s;
map$limit_ip$limit_key {
default$binary_remote_addr;
0 ""; # 白名单 key 为空
}
server {
location / {
limit_req zone=ip_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
}
注意:上面的写法有个小坑,limit_req_zone 的 key 用 $limit_ip,值为 0 时相当于没消耗桶。但更稳的写法是用 map 转 key:
nginx
http {
geo$is_internal {
default0;
10.0.0.0/8 1;
172.16.0.0/12 1;
192.168.0.0/16 1;
}
map$is_internal$limit_key {
default$binary_remote_addr;
1 ""; # 白名单
}
limit_req_zone$limit_key zone=ip_limit:10m rate=10r/s;
server {
location / {
limit_req zone=ip_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
}
5.5 限流 + 黑名单(特定 IP 拒绝)
nginx
http {
geo$is_black {
default0;
1.2.3.4 1; # 单个 IP
5.6.0.0/16 1; # IP 段
}
server {
location / {
if ($is_black) {
return444; # 静默断开
}
proxy_pass http://backend;
}
}
}
5.6 CC 攻击防护:UA 识别 + 限流
nginx
http {
# 识别常见攻击工具
map$http_user_agent$is_bot {
default0;
~*sqlmap1;
~*nmap1;
~*masscan1;
~*scrapy1;
~*httpclient1;
~*java1;
~*python-requests1;
}
# 没有 UA 字符串的也可能是攻击
map$http_user_agent$is_no_ua {
default0;
"" 1;
}
server {
location / {
if ($is_bot) {
return444;
}
if ($is_no_ua) {
return444;
}
proxy_pass http://backend;
}
}
}
注意:拦截 UA 可能误伤,部分合法客户端会不带 UA。
5.7 突发流量削峰:分阶段限流
nginx
http {
# 平时:每秒 1000 个
limit_req_zone$binary_remote_addr zone=normal_limit:10m rate=1000r/s;
# 限流:限制为平时一半
limit_req_zone$binary_remote_addr zone=peak_limit:10m rate=500r/s;
server {
location / {
# 默认用 normal
set$zone_name normal_limit;
set$zone_burst2000;
# 每天 9-10 点限流更严
if ($time_hour = "09") {
set$zone_name peak_limit;
set$zone_burst1000;
}
limit_req zone=$zone_name burst=$zone_burst nodelay;
proxy_pass http://backend;
}
}
}
注意:if 在 location 中有”邪恶 if”的坑,能用 map 就用 map:
nginx
http {
# 限流 zone 名称根据时间动态
map$time_hour$limit_zone {
default normal_limit;
"09" peak_limit;
"10" peak_limit;
"20" peak_limit;
}
limit_req_zone$binary_remote_addr zone=normal_limit:10m rate=1000r/s;
limit_req_zone$binary_remote_addr zone=peak_limit:10m rate=500r/s;
server {
location / {
limit_req zone=$limit_zone burst=2000 nodelay;
proxy_pass http://backend;
}
}
}
5.8 连接数限流:防慢速攻击
nginx
http {
limit_conn_zone$binary_remote_addr zone=conn_limit:10m;
limit_conn_zone$server_name zone=server_conn_limit:10m;
server {
location / {
# 单 IP 最多 10 个连接
limit_conn conn_limit 10;
# 单域名最多 1000 个连接
limit_conn server_conn_limit 1000;
proxy_pass http://backend;
}
}
}
5.9 限流 + 限速(带宽)
Nginx 自带 limit_rate:
nginx
server {
location /download/ {
# 单连接每秒最多 100KB
limit_rate 100k;
# 超过 1MB 之后开始限速
limit_rate_after 1m;
root /var/www;
}
}
5.10 限流日志与监控
nginx
http {
log_format limit_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'limit_req_status=$limit_req_status '
'limit_conn_status=$limit_conn_status '
'rt=$request_time '
'uaddr="$upstream_addr" '
'ustatus="$upstream_status"';
access_log /var/log/nginx/limit.log limit_log;
server {
location / {
limit_req zone=ip_limit burst=20 nodelay;
limit_conn conn_limit 100;
proxy_pass http://backend;
}
}
}
注意:$limit_req_status 是 Nginx 1.17.7+ 引入的变量,编译时需注意版本。
六、常用命令
6.1 Nginx 限流配置相关
bash
# 测试配置
nginx -t
# 平滑 reload
nginx -s reload
# 看配置
nginx -T | grep -E 'limit_req|limit_conn'
# 看错误日志
tail -F /var/log/nginx/error.log | grep -i limit
# 看 access log 中 429
awk '$9 == 429' /var/log/nginx/access.log | wc -l
# 看 429 Top IP
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# 看 429 Top URI
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
6.2 压测工具
6.2.1 ab(Apache Bench)
bash
# 安装
apt install apache2-utils
# 或
yum install httpd-tools
# 基本压测:1000 个请求,10 并发
ab -n 1000 -c 10 'http://api.example.com/test'
# 持续压测:60 秒
ab -t 60 -c 100 'http://api.example.com/test'
# 看详细输出
ab -n 1000 -c 10 -v 2 'http://api.example.com/test' 2>&1 | head -50
# 输出示例:
# Concurrency Level: 10
# Time taken for tests: 5.123 seconds
# Complete requests: 1000
# Failed requests: 0
# Total transferred: 123000 bytes
# HTML transferred: 12000 bytes
# Requests per second: 195.20 [#/sec] (mean)
# Time per request: 51.234 [ms] (mean)
# Time per request: 5.123 [ms] (mean, across all concurrent requests)
# Transfer rate: 23.45 [Kbytes/sec] received
# Connection Times (ms)
# min mean[+/-sd] median max
# Connect: 0 1 0.5 1 3
# Processing: 20 50 10.2 48 95
# Waiting: 5 15 8.7 14 35
# Total: 20 51 10.3 49 96
# Percentage of the requests served within a certain time (ms)
# 50% 49
# 66% 55
# 75% 60
# 80% 65
# 90% 75
# 95% 85
# 98% 95
# 99% 99
# 100% 96 (longest request)
6.2.2 wrk
bash
# 安装
apt install wrk
# 或编译安装
git clone https://github.com/wg/wrk.git
cd wrk && make
# 基本压测:4 线程 100 连接 30 秒
wrk -t 4 -c 100 -d 30s 'http://api.example.com/test'
# 看延迟分布
wrk -t 4 -c 100 -d 30s --latency 'http://api.example.com/test'
# 用 Lua 脚本自定义请求
wrk -t 4 -c 100 -d 30s -s post.lua 'http://api.example.com/api/post'
# post.lua
wrk.method = "POST"
wrk.body = '{"key":"value"}'
wrk.headers["Content-Type"] = "application/json"
6.2.3 vegeta
bash
# 安装
apt install vegeta
# 或
go install github.com/tsenart/vegeta@latest
# 创建 targets.txt
echo"GET http://api.example.com/test" > targets.txt
# 压测:每秒 1000 个请求,持续 30 秒
vegeta attack -duration=30s -rate=1000 -targets=targets.txt | vegeta report
# 输出 HTML 报告
vegeta attack -duration=30s -rate=1000 -targets=targets.txt | vegeta report -type=hist[0,50ms,100ms,200ms,500ms,1s,2s,5s]
# 输出文本
vegeta attack -duration=30s -rate=1000 -targets=targets.txt | vegeta report -type=text
6.2.4 hey
bash
# 安装
go install github.com/rakyll/hey@latest
# 压测
hey -n 1000 -c 10 'http://api.example.com/test'
6.3 IP 频次统计
bash
# 统计最近 1 万条日志中 Top 20 IP
awk '{print $1}' /var/log/nginx/access.log | tail -10000 | sort | uniq -c | sort -rn | head -20
# 统计最近 1 小时 Top 20 IP
awk -v cutoff="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$4 >= "["cutoff' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# 找 UA 为空的 IP(可能是攻击)
awk '$14 == "-" || $14 ~ /^- $|^-$/' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# 找 1 秒内请求超过 100 次的 IP
awk '{print $1, $4}' /var/log/nginx/access.log | \
awk '{print $1, substr($2, 14, 8)}' | \
sort | uniq -c | sort -rn | awk '$1 > 100' | head
6.4 防火墙联动(iptables / nftables)
把高频攻击 IP 加入防火墙:
bash
# 收集攻击 IP
ATTACK_IPS=$(awk '{print $1}' /var/log/nginx/access.log | tail -10000 | sort | uniq -c | sort -rn | head -20 | awk '$1 > 1000 {print $2}')
# 加入 iptables 黑名单
for ip in $ATTACK_IPS; do
iptables -I INPUT -s $ip -j DROP
done
# 保存
iptables-save > /etc/iptables/rules.v4
# 解除黑名单
iptables -D INPUT -s <ip> -j DROP
bash
# nftables 版本
nft add rule inet filter input ip saddr { 1.2.3.4, 5.6.7.8 } drop
注意:
- iptables 规则太多会影响性能
- 大量 IP 用 ipset 更高效
bash
# 用 ipset
ipset create blacklist hash:net
ipset add blacklist 1.2.3.4
ipset add blacklist 5.6.0.0/16
# iptables 引用
iptables -I INPUT -m set --match-set blacklist src -j DROP
6.5 临时封禁 IP(CC 应急)
bash
#!/bin/bash
# block_attack_ips.sh
# 用途:扫描最近高频 IP,加入 iptables 黑名单
# 用法:./block_attack_ips.sh /var/log/nginx/access.log 1000
LOG="${1:-/var/log/nginx/access.log}"
THRESHOLD="${2:-1000}"
TIME_RANGE_MIN="${3:-5}"# 最近 5 分钟
# 1. 拿到时间阈值
CUTOFF=$(date -d "$TIME_RANGE_MIN minutes ago"'+%d/%b/%Y:%H:%M')
# 2. 拿到攻击 IP
ATTACK_IPS=$(awk -v cutoff="$CUTOFF"'$4 >= "["cutoff'"$LOG" | \
awk '{print $1}' | sort | uniq -c | sort -rn | \
awk -v t="$THRESHOLD"'$1 > t {print $2}')
# 3. 加入黑名单
for ip in$ATTACK_IPS; do
if ! iptables -C INPUT -s "$ip" -j DROP 2>/dev/null; then
iptables -I INPUT -s "$ip" -j DROP
echo"$(date): Blocked $ip"
fi
done
# 4. 保存
iptables-save > /etc/iptables/rules.v4
注意:脚本必须按实际环境调整,特别是 iptables 命令。
七、配置示例
7.1 完整生产限流配置
nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
worker_rlimit_nofile65535;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections65535;
multi_accepton;
useepoll;
}
http {
include mime.types;
default_type application/octet-stream;
# 自定义日志格式
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" '
'urt="$upstream_response_time" '
'uaddr="$upstream_addr" '
'ustatus="$upstream_status" '
'limit_req=$limit_req_status '
'limit_conn=$limit_conn_status';
access_log /var/log/nginx/access.log main_ext;
sendfileon;
tcp_nopushon;
tcp_nodelayon;
keepalive_timeout65;
client_max_body_size20m;
client_body_buffer_size128k;
client_header_buffer_size4k;
large_client_header_buffers416k;
gzipon;
gzip_min_length1k;
gzip_buffers416k;
gzip_comp_level6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# ================== 限流配置开始 ==================
# 1. IP 黑名单
geo$is_black {
default0;
# 1.2.3.4 1;
# 5.6.0.0/16 1;
}
# 2. IP 白名单
geo$is_internal {
default0;
10.0.0.0/8 1;
172.16.0.0/12 1;
192.168.0.0/16 1;
127.0.0.1 1;
}
# 3. 白名单转换 key
map$is_internal$limit_key {
default$binary_remote_addr;
1 "";
}
# 4. UA 识别
map$http_user_agent$is_bad_ua {
default0;
~*sqlmap1;
~*nmap1;
~*masscan1;
~*scrapy1;
~*httpclient1;
~*java1;
~*python-requests1;
~*go-http-client1;
}
# 5. 限流 zone 定义
# 全局限流:每 IP 每秒 100 个
limit_req_zone$limit_key zone=global_limit:10m rate=100r/s;
# API 限流:每 IP 每秒 50 个
limit_req_zone$limit_key zone=api_limit:10m rate=50r/s;
# 登录限流:每 IP 每秒 1 个
limit_req_zone$limit_key zone=login_limit:5m rate=1r/s;
# 静态资源限流:每 IP 每秒 200 个
limit_req_zone$limit_key zone=static_limit:10m rate=200r/s;
# 6. 连接数限流
limit_conn_zone$limit_key zone=conn_limit:10m;
limit_conn_zone$server_name zone=server_conn_limit:10m;
# 7. 返回 429 而不是 503
limit_req_status429;
limit_conn_status429;
limit_req_log_levelwarn;
limit_conn_log_levelwarn;
# ================== 限流配置结束 ==================
upstream backend {
least_conn;
server10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server10.0.0.12:8080 max_fails=3 fail_timeout=30s;
server10.0.0.13:8080 max_fails=3 fail_timeout=30s;
keepalive32;
}
server {
listen80;
server_name api.example.com;
# 健康检查
location = /health {
access_logoff;
return200"ok\n";
}
# 黑名单
if ($is_black) {
return444;
}
# 异常 UA
if ($is_bad_ua) {
return444;
}
# 登录接口
location /api/login {
limit_req zone=login_limit burst=5 nodelay;
limit_conn conn_limit 10;
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态资源
location /static/ {
limit_req zone=static_limit burst=400 nodelay;
alias /var/www/static/;
expires30d;
access_logoff;
}
# 默认 API
location / {
limit_req zone=api_limit burst=100 nodelay;
limit_conn conn_limit 50;
proxy_pass http://backend;
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_http_version1.1;
proxy_set_header Connection "";
proxy_connect_timeout5s;
proxy_read_timeout60s;
proxy_send_timeout60s;
}
}
}
7.2 OpenResty + Redis 复杂限流
用 OpenResty + Redis 实现分布式限流(多机共享):
nginx
# /usr/local/openresty/nginx/conf/nginx.conf
http {
lua_shared_dict my_limit 10m;
init_worker_by_lua_block {
local redis = require "resty.redis"
local ok, err = redis.connect("10.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "redis connect error: ", err)
end
}
access_by_lua_block {
local key = "rate_limit:" .. ngx.var.remote_addr
local now = ngx.now() * 1000
local window = 60000 -- 1 分钟
local limit = 100 -- 100 个请求
local redis = require "resty.redis"
local ok, err = redis.connect("10.0.0.1", 6379)
if not ok then
-- Redis 不可用时放行
return
end
local res, err = redis:eval([[
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZADD', key, now, now)
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
redis.call('PEXPIRE', key, window)
if count > limit then
return 0
else
return 1
end
]], 1, key, now, window, limit)
if not res or res == 0 then
ngx.status = 429
ngx.say("rate limit exceeded")
return ngx.exit(429)
end
}
server {
listen80;
server_name api.example.com;
location / {
proxy_pass http://backend;
}
}
}
注意:OpenResty 连接 Redis 必须配置连接池,避免频繁建连。
7.3 Tengine 主动健康检查
Tengine 自带健康检查模块:
nginx
# /etc/nginx/nginx.conf
http {
upstream backend {
server10.0.0.11:8080;
server10.0.0.12:8080;
server10.0.0.13:8080;
check interval=3000 rise=2 fall=5 timeout=2000 type=http default_down=true;
check_http_send"GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
}
7.4 限流 fallback 页面
nginx
http {
limit_req_zone$binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
listen80;
server_name api.example.com;
# 限流错误页
error_page429 /errors/429.html;
location = /errors/429.html {
internal;
root /var/www/errors;
}
location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
}
429.html 内容示例:
html
<!DOCTYPE html>
<html>
<head>
<title>Too Many Requests</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #d9534f; }
p { color: #666; }
</style>
</head>
<body>
<h1>429 Too Many Requests</h1>
<p>请求过于频繁,请稍后重试。</p>
<p>Request rate limit exceeded. Please try again later.</p>
</body>
</html>
7.5 限流 + JSON 返回
nginx
http {
limit_req_zone$binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
listen80;
server_name api.example.com;
location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
# 限流时返回 JSON
error_page429 = @rate_limit;
}
location@rate_limit {
default_type application/json;
return429'{"code":429,"message":"rate limit exceeded","retry_after":60}';
}
}
}
八、日志与指标观察方法
8.1 关键日志
8.1.1 Nginx 限流日志
bash
# 限流事件(Nginx 默认 error log)
tail -F /var/log/nginx/error.log | grep -i 'limiting requests'
# 输出示例:
# 2024/01/01 10:30:01 [warn] 1234#0: *5678 limiting requests, excess: 20.000 by zone "api_limit", client: 1.2.3.4, server: api.example.com, request: "GET /api/data HTTP/1.1"
8.1.2 自定义日志
nginx
log_format limit_log '$remote_addr - [$time_local] '
'$request $status '
'limit_req=$limit_req_status '
'limit_conn=$limit_conn_status '
'rt=$request_time';
注意:$limit_req_status 是 Nginx 1.17.7+ 才有的变量。
8.2 关键指标
8.2.1 限流相关指标
- 429 比例
- 各 URI 的 429 比例
- 各 IP 的 429 次数
- 限流错误日志条数
- limit_req zone 内存使用
8.2.2 Prometheus 抓取
yaml
# /etc/prometheus/prometheus.yml
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
8.2.3 关键 PromQL
promql
# 429 比例
sum(rate(nginx_http_requests_total{status="429"}[5m]))
/ sum(rate(nginx_http_requests_total[5m]))
# 各 URI 的 429 率
sum by (uri) (rate(nginx_http_requests_total{status="429"}[5m]))
# 各 IP 的 429 次数
sum by (client_ip) (rate(nginx_http_requests_total{status="429"}[5m]))
8.2.4 告警规则
yaml
groups:
-name:nginx_limit
rules:
-alert:Nginx429Spike
expr:rate(nginx_http_requests_total{status="429"}[1m])>100
for:1m
labels:
severity:warning
annotations:
summary:"429 错误数飙升,可能是限流触发"
-alert:Nginx429Ratio
expr:|
sum(rate(nginx_http_requests_total{status="429"}[5m]))
/
sum(rate(nginx_http_requests_total[5m]))
> 0.05
for:2m
labels:
severity:critical
annotations:
summary:"429 比例超过 5%,可能限流过严"
-alert:NginxConnectionHigh
expr:nginx_connections_active>50000
for:1m
labels:
severity:warning
annotations:
summary:"Nginx 活跃连接数过高"
注意:阈值必须按业务基线调整。
九、排查路径
9.1 故障排查案例 1:429 飙升
现象:
- 告警:429 错误数飙升
- 业务反馈:用户登录失败
初步判断:
- 限流配置过严
- 业务量突增
- 客户端有重试风暴
命令检查:
bash
# 1. 看 429 比例
awk '$9 == 429' /var/log/nginx/access.log | wc -l
# 2. 看 429 Top IP
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# 3. 看 429 Top URI
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
# 4. 看 429 时间分布
awk '$9 == 429' /var/log/nginx/access.log | awk '{print $4}' | cut -c14-21 | uniq -c
# 5. 看错误日志
tail -F /var/log/nginx/error.log | grep -i limit
关键指标:
- 429 Top IP:集中在某几个 IP
- 429 Top URI:集中在某个接口
- 错误日志:limiting requests by zone
根因定位:
- 单 IP 高频 → 客户端有重试循环
- 单 URI 高频 → 接口被刷
修复方案:
- 短期:调整 limit_req 的 rate
- 中期:客户端重试退避
- 长期:分布式限流
验证:
- 429 比例下降
- 业务反馈恢复
- 客户端日志确认
回滚:
- 临时注释 limit_req
- reload 回去
9.2 故障排查案例 2:被 CC 攻击
现象:
- 告警:Nginx 带宽打满
- 告警:upstream QPS 突增
- 业务反馈:服务慢
初步判断:
- CC 攻击
- 爬虫狂扫
命令检查:
bash
# 1. 看 Top IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 2. 看 Top UA
awk '{print $14}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 3. 看请求时间分布
awk '{print $4}' /var/log/nginx/access.log | cut -c14-21 | uniq -c
# 4. 看异常请求
awk '$14 ~ /python|java|sqlmap|scrapy/' /var/log/nginx/access.log | head
关键指标:
- Top IP 集中度高
- 异常 UA 占比高
- 请求时间分布不均
根因定位:
- 大量同 IP 或同 IP 段的高频请求
- 没有 UA 或 UA 是常见攻击工具
修复方案:
nginx
# 1. 启用 UA 拦截
map$http_user_agent$is_bad_ua {
default0;
~*sqlmap1;
~*nmap1;
~*scrapy1;
~*python-requests1;
~*go-http-client1;
}
server {
if ($is_bad_ua) {
return444;
}
}
# 2. 加 iptables 黑名单
iptables -I INPUT -s 1.2.3.4 -j DROP
# 3. 启用严格限流
limit_req zone=ip_limit burst=5 nodelay;
验证:
- 攻击 IP 减少
- 5xx 下降
- 带宽恢复
回滚:
- 调整限流 rate
- 解除部分 IP 黑名单
9.3 故障排查案例 3:突发流量
现象:
- 告警:Nginx 5xx 飙升
- 业务方:限时活动开始
初步判断:
- 突发流量超过平时容量
- 上游服务响应慢
命令检查:
bash
# 1. 看 QPS 变化
awk '{print $4}' /var/log/nginx/access.log | cut -c14-21 | uniq -c
# 2. 看 5xx 比例
awk '$9 ~ /^5/' /var/log/nginx/access.log | wc -l
# 3. 看 upstream 响应时间
awk '{print $urt}' /var/log/nginx/access.log | sort -rn | head -20
关键指标:
- QPS 突增
- upstream 响应时间变长
- 5xx 比例升高
根因定位:
- 突发流量超过平时 10 倍
- 限流没启用或 burst 太大
修复方案:
- 立即启用严格限流
- 扩容上游
- 客户端降级
nginx
# 立即启用
limit_req zone=api_limit burst=10 nodelay;
验证:
- 5xx 下降
- 业务恢复
回滚:
- 活动结束后调宽限流
- 灰度恢复
十、风险提醒
10.1 限流配置的风险
-
rate 太小
:误伤正常用户
-
rate 太大
:起不到限流作用
-
burst 太大
:突发流量全部放行
-
burst 太小
:正常请求被拒绝
-
zone 太小
:内存溢出或 key 淘汰
-
key 选错
:限流维度不对
10.2 限流变更的风险
- 修改 limit_req 前要压测
- 修改后要灰度
- 出现误伤要快速回滚
- 配合客户端重试退避
10.3 限流与上游联动
- 限流不能让上游变快
- 限流挡住的请求要让客户端感知
- 限流挡住的请求要记日志
- 限流挡住的请求要有降级页面
10.4 限流与监控
- 429 比例要监控
- 限流错误日志要监控
- 上游 QPS 要监控
- 客户端重试比例要监控
10.5 限流与安全
- 限流不能替代 WAF
- 限流不能替代 IP 黑名单
- 限流不能替代验证码
- 限流不能替代 HTTPS
十一、验证方式
11.1 配置验证
bash
# 1. 测试配置语法
nginx -t
# 2. 模拟压测
wrk -t 4 -c 100 -d 30s 'http://api.example.com/test'
# 3. 看 429 比例
echo "Total: $(wc -l < /var/log/nginx/access.log)"
echo "429: $(awk '$9 == 429' /var/log/nginx/access.log | wc -l)"
11.2 灰度验证
bash
# 1. 在单节点启用
# 修改配置
nginx -t && nginx -s reload
# 2. 持续压测
wrk -t 4 -c 100 -d 60s 'http://<single_node_ip>/test'
# 3. 看响应码
# 应该有 200 + 部分 429
# 4. 持续 5 分钟后扩展到全量
11.3 灰度上线流程
bash
# 1. 准备配置
cat > /etc/nginx/conf.d/limit_v2.conf <<'EOF'
limit_req_zone $binary_remote_addr zone=api_v2:10m rate=20r/s;
EOF
# 2. 在测试节点加载
# 修改配置 -> nginx -t -> nginx -s reload
# 3. 持续观察
tail -F /var/log/nginx/access.log | grep 'status="429'
# 4. 5 分钟后扩展
# 通过配置中心 / ansible 推送到所有节点
# 5. 全量后持续观察
十二、回滚方案
12.1 配置回滚
bash
# 1. 修改前备份
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d_%H%M%S)
# 2. 修改后测试
nginx -t
# 3. 平滑 reload
nginx -s reload
# 4. 出问题立即回滚
cp /etc/nginx/nginx.conf.bak.<timestamp> /etc/nginx/nginx.conf
nginx -t && nginx -s reload
12.2 关闭限流
bash
# 临时关闭(注释 limit_req)
sed -i 's/^ limit_req/# &/' /etc/nginx/nginx.conf
nginx -t && nginx -s reload
# 紧急关闭(直接去掉 zone)
# 但要去掉所有引用
12.3 调整限流参数
bash
# 把 rate 调宽(限流放宽)
# 改前
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# 改后
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
nginx -t && nginx -s reload
12.4 紧急情况
如果限流配置错误导致大面积 429:
bash
# 1. 立即恢复
cp /etc/nginx/nginx.conf.bak.<timestamp> /etc/nginx/nginx.conf
nginx -t && nginx -s reload
# 2. 确认恢复
curl -i 'http://api.example.com/test'
# 3. 通知业务方
十三、生产环境注意事项
13.1 限流参数调优
- 拿监控数据,按业务量设置
- 拿压测数据,验证限流效果
- 拿真实数据,调整 burst
- 持续观察,按业务增长调整
13.2 限流与客户端配合
- 客户端要做好 429 退避
- 客户端要做好超时控制
- 客户端要有限流提示
- 客户端要有降级方案
13.3 限流与上游配合
- 上游要有降级方案
- 上游要有缓存
- 上游要有限流(应用层)
- 上游要有熔断
13.4 限流与监控
- 429 比例监控
- 限流错误日志监控
- 上游 QPS 监控
- 客户端重试监控
13.5 限流与灰度
- 单节点验证
- 部分流量验证
- 全量上线
- 持续观察
13.6 限流与安全
- WAF 配合
- IP 黑名单
- 验证码
- HTTPS
13.7 限流与高可用
- 多机房限流(每机房独立)
- 多集群限流(每集群独立)
- 全局限流(用 OpenResty + Redis)
- 动态限流(按业务量自动调整)
13.8 限流与容量
- 容量评估
- 弹性扩容
- 应急预案
- 故障演练
十四、总结
Nginx 限流是接入层必备的能力。这一篇把核心讲清楚了:
基础概念:
- limit_req_zone、limit_req、limit_conn_zone、limit_conn
- burst、nodelay、delay 的差别
- 漏桶算法
- key 的选择
实战配置:
- 按 IP 限流
- 按 URI 限流
- 多级限流
- 白名单、黑名单
- CC 攻击防护
- 突发流量削峰
- OpenResty + Redis 复杂限流
生产实践:
- 压测验证
- 灰度上线
- 监控告警
- 回滚方案
关键参数:
- rate:按真实业务量 + 冗余
- burst:按允许的突发 + 排队容忍
- zone:按 key 数量 × 16KB
多层防护:
- Nginx 接入层
- 应用层(Sentinel / Guava)
- 中间件层(Redis 令牌桶)
- 数据库层(连接池)
面试要点:
- 限流算法(漏桶 vs 令牌桶)
- 限流配置(limit_req + burst + nodelay)
- 限流参数(rate、burst、key)
- 限流场景(CC、突发、爬虫)
- 限流与降级、熔断的关系
最终验收点:
- 拿到一个被 CC 攻击的告警,能否 10 分钟内上线防护
- 拿到一个 429 飙升的告警,能否定位是配置问题还是客户端问题
- 拿到一个限流参数调优需求,能否按业务量算出合理的 rate 和 burst
如果能做到这三点,这篇文章的价值就达到了。
文末福利
今天给大家分享一份超级牛掰的Linux学习笔记,足足有1456页!是一位Linux运维大佬整理分享的,分享是获得大佬同意的,大家有需要的尽管收藏起来!
笔记介绍
这份笔记非常全面且详细,从Linux基础到shell脚本,再到防火墙、数据库、日志服务管理、Nginx、高可用集群、Redis、虚拟化、Docker等等,与其说Linux学习笔记,不如说是涵盖了运维各个核心知识。
并且图文并茂,代码清晰,每一章下面都有更具体详细的内容,十分适合Linux运维学习参考!
笔记展示
笔记下载
扫描下方二维码,回复暗号“1456页Linux笔记“,即可100%免费领取成功
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:马哥Linux运维 点击关注 👉 点击关注 👉《Nginx 限流实战:如何优雅地防御 CC 攻击和突发流量》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论