0

Nginx请求限流配置实战:limit_req防CC攻击与接口保护的完整指南

2026.05.22 | youres | 11次围观

目录

为什么要配置Nginx请求限流

网站上线后,迟早会遇到恶意刷接口、CC攻击、爬虫疯狂抓取的情况。不做限流,服务器资源会被瞬间耗尽,正常用户访问不了。Nginx的limit_req模块就是专门干这件事的——按规则限制请求速率,超出的直接拒绝或排队。

和连接数限制(limit_conn)不同,请求限流关注的是单位时间内的请求次数,更适合防护高频短连接的攻击方式。

limit_req核心原理:漏桶算法

Nginx请求限流用的是漏桶算法(Leaky Bucket)。简单说就是:请求像水往桶里倒,桶以固定速率往外漏水。桶满了,多余的水直接溢出(请求被拒绝)。

这个算法的好处:

  • 平滑输出:不管进来多少请求,处理速率始终稳定
  • 允许突发:burst参数就是桶的容量,短时间内多几个请求可以排队等待
  • 超限拒绝:排队也满了,就返回503

比起令牌桶算法,漏桶更保守,不会突然放一大批请求出去,适合防护场景。

基础配置:limit_req_zone定义限流区域

限流的第一步是在http块里定义限流区域:

http {
    # 按IP限流,每秒1个请求,分配10m共享内存
    limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;
    
    # 按服务器名限流,每秒10个请求
    limit_req_zone $server_name zone=perserver:10m rate=10r/s;
}

参数解释:

  • $binary_remote_addr:用二进制格式的客户端IP作为限流key,比字符串格式省内存(1m内存大约能存16000个IP)
  • zone=perip:10m:共享内存区域名称和大小,10m约记录16万个IP
  • rate=1r/s:每秒允许1个请求,也可以写rate=30r/m(每分钟30个)

注意:limit_req_zone必须放在http块里,不能放在server或location里。

burst与nodelay:突发流量怎么处理

光设rate=1r/s太严格了——正常用户快速点击页面可能1秒发3个请求,第2、3个就会被拒绝。所以需要burst参数来放行突发请求:

location /api/ {
    limit_req zone=perip burst=5;
}

burst=5的意思:允许排队5个超额请求。超出rate的请求不直接拒绝,而是排队等处理。但排队的请求会被延迟处理——每秒处理1个排队请求。

如果你不想让请求排队等待,加nodelay

location /api/ {
    limit_req zone=perip burst=5 nodelay;
}

nodelay的作用:排队中的请求立即处理,不延迟。桶里5个突发请求一口气放出去,然后恢复1r/s的速率。适合用户体验优先的场景。

三者区别一句话

  • 只设rate → 超限立即503
  • rate+burst → 超限排队延迟处理
  • rate+burst+nodelay → 超限立即处理但桶满才503

实战方案一:API接口限流

API接口最需要限流,防止被恶意调用刷数据:

http {
    # API限流:每秒5个请求,允许突发10个
    limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
    
    server {
        location /api/v1/ {
            limit_req zone=api burst=10 nodelay;
            limit_req_status 429;
            
            # 自定义429错误页面
            error_page 429 /429.html;
        }
        
        location = /429.html {
            internal;
            return 429 '{"error":"rate_limit","message":"Too many requests"}';
            add_header Content-Type application/json;
        }
    }
}

这里把限流状态码改成了429(Too Many Requests),比503更语义化。API返回JSON格式错误信息,前端也好处理。

实战方案二:全站防刷与CC防护

CC攻击的特征是大量IP同时高频访问,单个IP可能不明显,但总量很大。对付这种攻击需要多维度限流

http {
    # 单IP限流
    limit_req_zone $binary_remote_addr zone=cc_perip:10m rate=2r/s;
    # 单服务器限流
    limit_req_zone $server_name zone=cc_perserver:20m rate=100r/s;
    
    server {
        # 双重限流:IP级别+服务器级别
        limit_req zone=cc_perip burst=10 nodelay;
        limit_req zone=cc_perserver burst=50;
        
        limit_req_status 503;
        limit_req_log_level warn;
    }
}

两层限流同时生效:单IP超2r/s触发第一层,全站超100r/s触发第二层。CC攻击一般单IP频率不高但总量大,perserver限流刚好能挡住。

limit_req_log_level warn:被限流的请求记录为warn级别日志,方便监控。

实战方案三:按URI分级限流

不同接口需要不同限流策略——登录接口要严,搜索接口可以宽:

http {
    limit_req_zone $binary_remote_addr zone=login:5m rate=1r/m;
    limit_req_zone $binary_remote_addr zone=search:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=static:10m rate=50r/s;
    
    server {
        # 登录:每分钟1次,防暴力破解
        location /login {
            limit_req zone=login burst=3 nodelay;
        }
        
        # 搜索:每秒10次,允许突发
        location /search {
            limit_req zone=search burst=20 nodelay;
        }
        
        # 静态资源:宽松限流
        location /static/ {
            limit_req zone=static burst=100 nodelay;
        }
    }
}

登录接口用1r/m(每分钟1次),暴力破解基本不可能。burst=3 nodelay允许3次排队快速处理,但1分钟内第4次登录直接拒绝。

自定义返回状态码和错误页面

默认限流返回503,但你可以改成更合适的:

# 改成429
limit_req_status 429;

# 配合自定义错误页面
error_page 429 /rate_limit.html;

location = /rate_limit.html {
    internal;
    root /var/www/error_pages;
}

也可以根据限流类型返回不同页面:API返回JSON,普通页面返回HTML提示。

限流日志级别与监控

限流日志是排查问题和监控攻击的重要依据:

# 限流拒绝 → error级别,限流延迟 → warn级别
limit_req_log_level warn;

# 查看限流日志
grep "limiting requests" /var/log/nginx/error.log

日志会记录被限流的IP、zone名称和超出速率。定期统计这些数据可以识别攻击来源。

更高级的做法是用dry run模式(1.17.1+版本支持):

limit_req_dry_run on;

dry run模式下请求不会被拒绝,但日志照常记录限流事件。适合调试阶段,先观察限流效果再正式启用。

常见坑与排查技巧

坑1:限流区域内存不够

zone大小设太小,IP记录满了后新IP的限流失效。1m约存16000个二进制IP,10m够16万个。站点流量大就多分配。

坑2:反向代理后$remote_addr全是代理IP

Nginx做反向代理时,所有请求的IP都是上游代理的IP,限流等于对代理限流,后面的真实客户端全被误杀。解决方法:

# 用真实客户端IP
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;

limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;

坑3:burst设太大等于没限流

burst=1000配合nodelay,等于允许瞬间1000个请求全部放行,限流形同虚设。burst一般设5-20就够了。

排查技巧

  • 先开dry_run观察,确认限流规则生效
  • 检查error.log里的limiting requests记录
  • 用ab或wrk压测验证限流效果

相关文章推荐

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论