利用nginx限流防止CC攻击
今天用户突然反馈页面转圈不显示。这时候监控也开始报警,我立刻上服务器上看服务器负载非常高,网站响应时间飙升到10秒以上。
nginx的error日志里大量499状态码,CPU和网络IO都爆了,服务器非常卡。
top命令看到nginx worker进程吃满了CPU,还有nginx有大量的访问记录,判定是有人刷接口导致的。要让开发人员立刻写程序阻止刷接口感觉来不及,就使用了nginx限流。
之前知道nginx限流,但是没有实操过,特此记录下来过程,以便下次急用找不到。
这次在nginx里加了限流配置,主要用了以下参数:
limit_req_zone用来定义限流规则。我们按IP限流,zone=one表示共享内存区名字是one,10m是共享内存大小,rate=10r/s表示每秒允许10个请求。
limit_req是实际应用限流,zone=one对应上面的定义,burst=20是允许突发流量最大20个请求,nodelay表示不延迟处理突发请求。
limit_conn_zone定义连接数限制,zone=addr:10m表示用客户端IP作为key,共享内存10m。
limit_conn是实际应用连接数限制,perip 20表示每个IP最多20个连接。
配置完reload nginx后,效果立竿见影。CPU负载从100%慢慢降到30%左右,网站逐渐恢复可用。观察了一会儿,正常用户访问基本不受影响,攻击流量被有效拦截。
这次用到的几个关键参数解释:
limit_req_zone的rate参数很重要,要根据业务特点调整。我们电商网站首页10r/s够用,API接口可能要设更高。
burst控制突发流量,设太小会影响正常用户短时间内多次操作,比如下单流程。
nodelay让突发请求不被延迟,但会立即占用burst配额,需要权衡。
limit_conn的连接数限制对防CC很有效,但要注意不要设太低,否则会影响多标签页访问的用户。
后来分析日志发现攻击源主要是几个IP段,顺手在nginx里deny掉了。不过限流才是治本的方法,毕竟攻击者换个IP就能继续。
这次应急还算顺利,nginx的限流模块确实好用。记录下这些参数,下次再遇到直接复制改改就能用。运维嘛,最重要的就是经验复用。
以下是网上找的资料,解释很详细,特此记录下来备用:
http {
# 定义请求限流zone(基于客户端IP)
# $binary_remote_addr:用二进制格式存储客户端IP,节省空间
# zone=req_limit:10m:创建名为req_limit的共享内存区,10MB大小
# rate=10r/s:限制每秒10个请求(也可以用r/m表示分钟)
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# 定义连接数限制zone
# zone=conn_limit:10m:创建名为conn_limit的共享内存区
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
server_name example.com;
# 应用请求限流
# zone=req_limit:使用前面定义的限流zone
# burst=20:允许突发20个请求排队
# nodelay:不延迟处理突发请求(立即扣减burst计数)
limit_req zone=req_limit burst=20 nodelay;
# 应用连接数限制
# 每个IP同时最多保持20个连接
limit_conn conn_limit 20;
# 可选:对特定URI做更严格的限制
location /api/ {
limit_req zone=req_limit burst=5;
limit_conn conn_limit 5;
}
# 可选:白名单设置(不限制内网IP)
location / {
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 20;
# 192.168.1.0/24网段不受限
if ($remote_addr ~ "^192\.168\.1") {
set $limit_pass 1;
}
if ($limit_pass = 1) {
limit_req off;
limit_conn off;
}
}
# 可选:返回429状态码时的自定义错误页
error_page 429 /too_many_requests;
location = /too_many_requests {
internal;
return 429 '{"error": "请求过于频繁"}';
}
}
}