利用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": "请求过于频繁"}';
        }
    }
}
posted @ 2025-06-04 15:12  99客服  阅读(72)  评论(0)    收藏  举报