Nginx 限流防 CC 攻击实战:limit_req、limit_conn 配置与排错指南

个人网站的流量不大,但被攻击的概率一点都不小。网站刚有点收录,评论区突然涌进几百条垃圾信息,或者某天打开后台发现服务器 CPU 飙到 100%,再一看访问日志,全是同一个接口被反复请求——这就是典型的 CC 攻击。CC 攻击不像 DDoS 那样用巨大流量把带宽打满,它模拟正常用户的行为,用大量看似合法的请求去消耗 PHP 进程、数据库连接和 CPU,个人站长的小 VPS 往往几分钟就被拖垮。好消息是,Nginx 自带的 limit_req 和 limit_conn 模块就是成本最低、见效最快的第一道防线,不需要装任何第三方软件。这篇文章把两个模块的配置、参数含义、精细化限流和常见坑一次讲透。

一、先分清 CC 攻击和 DDoS 攻击

DDoS 攻击的目标是带宽和网络设备,攻击者用海量流量把服务器的上行带宽占满,特征是流量异常巨大、所有服务都连不上。CC 攻击(Challenge Collapsar)属于应用层攻击,流量不一定大,但请求频率和并发数很高,目标直指动态页面:登录接口、搜索页、评论提交、文章列表,每一个请求都要经过 PHP 解析、数据库查询,几个请求就能占住一个 PHP-FPM 进程。当攻击者的并发数超过 PHP-FPM 的 max_children 时,正常用户的所有请求都要排队,网站表现就是"转圈圈、打不开、502 连环弹"。

防护思路也因此不同:防 DDoS 要靠机房清洗和 CDN 高防,而防 CC 主要靠应用层的限流、验证码和封禁。Nginx 的限流模块就是应用层防线的第一块砖。

二、limit_req:请求频率限制

limit_req 模块(ngx_http_limit_req_module)基于漏桶算法:请求以固定的速率被处理,超过速率的请求先进入桶里排队,桶满了就直接拒绝。第一步是在 http 块里定义一个限流区域,区域要指定键(key)和速率:

http {
    limit_req_zone $binary_remote_addr zone=req_zone:10m rate=5r/s;
}

$binary_remote_addr 是客户端的 IP 二进制形式,作为限流的键,意思就是"每个 IP 单独计数";zone=req_zone:10m 给这个计数器分配 10MB 共享内存,大约能存 16 万个 IP 的状态;rate=5r/s 表示每个 IP 每秒最多处理 5 个请求。速率也支持分钟为单位,比如 rate=30r/m。然后在 server 或 location 里启用:

server {
    limit_req zone=req_zone burst=10 nodelay;
}

burst=10 是突发容量,允许瞬间多出 10 个请求排队处理;nodelay 表示突发部分的请求不排队等待,直接按当前速率处理。对个人博客来说,rate=5r/s 已经非常宽裕——正常用户就算疯狂刷新,一秒也到不了 5 次,而攻击脚本动辄每秒几十上百次请求,会被立刻拦截。如果不加 nodelay,超出速率的请求会排队延迟处理,用户会感觉页面变慢;加上 nodelay 后,超过 burst 容量的请求直接返回 503。

三、limit_conn:并发连接限制

limit_req 管的是"每秒来几个请求",limit_conn 管的是"同一时刻挂着几个连接"。有些攻击不走频率,而是建立大量连接慢慢挂着,把 Nginx 的连接数上限和系统文件句柄耗尽。配置方式和 limit_req 类似:

http {
    limit_conn_zone $binary_remote_addr zone=conn_zone:10m;
}
server {
    limit_conn conn_zone 10;
}

每个 IP 最多同时保持 10 个连接。对于有大量图片、CSS、JS 的站点,10 个连接对正常浏览器来说绰绰有余(浏览器对同一域名的并发连接本身也就 6 个左右),却能有效挡住连接耗尽型攻击。两个模块建议同时启用:频率和并发一起限,攻击者既不能狂刷,也不能挂连接。

四、精细化限流:高危接口单独设限

全站统一限流有个问题:文章页访问量大,登录接口访问量小,用同一套速率要么误伤正常读者,要么挡不住针对登录接口的爆破。更合理的做法是用 map 按请求 URI 分配不同的限流区域:

http {
    limit_req_zone $binary_remote_addr zone=req_zone:10m rate=5r/s;
    limit_req_zone $binary_remote_addr zone=login_zone:10m rate=1r/s;

    map $request_uri $limit_key {
        default          req_zone;
        ~^/admin/login   login_zone;
        ~^/action/login  login_zone;
    }
}
server {
    limit_req zone=$limit_key burst=5 nodelay;
}

这样普通页面走 req_zone(每秒 5 个),登录接口走 login_zone(每秒 1 个)。登录接口 1r/s 完全不影响手工输入用户名密码,但能把脚本爆破挡在门外。Typecho 的后台登录地址、评论提交接口、搜索接口都是 CC 和爆破的重灾区,建议都单独给一个更严格的限流区域。注意 map 的正则匹配是区分大小写的,如果接口路径里有大写字母,可以加 ~* 前缀改成不区分大小写。

五、自定义限流响应

被限流的请求默认返回 503 Service Unavailable,浏览器会显示一个简单的错误页。个人站长可以做得更友好一点,准备一个自定义的 503 页面,顺便告诉用户"访问太频繁,稍后再试":

server {
    limit_req_status 503;
    error_page 503 /503.html;
    location = /503.html {
        root /var/www/html;
        internal;
    }
}

如果站点有 API 接口,被限流时返回一个 JSON 更规范,HTTP 状态码用 429(Too Many Requests)语义也更准确:

server {
    error_page 429 = @rate_limited;
    location @rate_limited {
        default_type application/json;
        return 429 '{"code":429,"msg":"请求过于频繁,请稍后再试"}';
    }
}

需要注意,限流状态码和 error_page 要配对使用,改了 limit_req_status 却忘了 error_page,用户看到的还是默认错误页。

六、配合 realip、fail2ban 做纵深防御

如果网站套了 CDN(Cloudflare 或其他国内 CDN),$binary_remote_addr 拿到的是 CDN 节点的 IP,而不是访客的真实 IP。所有访客共享同一个 CDN 出口 IP,限流会集体误伤:正常用户和攻击者一起被限。这种情况必须启用 realip 模块,把真实 IP 解析出来:

server {
    set_real_ip_from 103.21.244.0/22;   # CDN 的 IP 段,按实际填写
    set_real_ip_from 103.22.200.0/22;
    real_ip_header CF-Connecting-IP;    # Cloudflare 用这个,其他 CDN 一般用 X-Forwarded-For
    real_ip_recursive on;
}

其次,把限流日志接入 fail2ban:Nginx 在限流时会往错误日志写 "limiting requests, excess: ... by zone" 的记录,写一个 fail2ban 的 filter 匹配这段日志,让被限流多次的 IP 自动封禁一段时间,攻击者被限流后还会被防火墙直接拉黑,双重拦截。最后别忘了应用层的兜底:Typecho 后台可以装登录验证码插件,评论可以加验证码和频率限制,搜索接口加个简单的人机校验。限流挡住大部分自动化攻击,验证码和封禁处理漏网之鱼,这一套纵深下来,个人网站的 CC 防线基本就稳了。

还有一类流量千万不能误伤:搜索引擎的爬虫。百度蜘蛛、Googlebot、bingbot 抓取页面时请求频率很高,如果限流把爬虫也挡了,轻则收录变慢,重则整站收录下降,那就得不偿失了。解决思路是用 map 按 User-Agent 把蜘蛛分流到单独的宽松限流区域,蜘蛛给 20r/s,正常访客保持 5r/s:

map $http_user_agent $req_zone {
    default       req_zone;
    ~*Baiduspider spider_zone;
    ~*Googlebot   spider_zone;
    ~*bingbot     spider_zone;
}
limit_req_zone $binary_remote_addr zone=spider_zone:10m rate=20r/s;

server 里的 limit_req zone=$req_zone burst=10 nodelay; 保持不变,蜘蛛和访客各走各的限流区域,互不干扰。判断蜘蛛要注意正则的 ~* 是不区分大小写的匹配,实际蜘蛛 UA 里的字母大小写并不固定,用 ~* 更稳妥。

七、常见踩坑与排错

配置限流后最常见的几个问题:第一,改完配置忘了执行 nginx -t 检查语法、nginx -s reload 重载,配置自然不生效,改完一定要两步都做;第二,burst 设置过大,比如 burst=1000,等于给攻击者留了个大水池,限流形同虚设,burst 建议 5 到 20 之间,先小后大观察调整;第三,limit_req 放在 server 层会把静态资源也一起限了,图片和 CSS 完全没必要限,建议把限流指令只放在 PHP 解析的 location(比如 location ~ \.php$)和高危接口的 location 里,静态资源靠 expires 缓存处理;第四,限流日志刷屏,把 limit_req_log_level 设为 notice 即可,正常流量不会产生日志,只有真正触发限流才记录;第五,分清限流和故障的区别,被限流返回的是 503,如果网站报 502,那是后端 PHP-FPM 的问题,跟限流无关,别被误导去调限流参数。

八、总结

对个人站长来说,CC 攻击防不胜防,但完全可以在 Nginx 层用两个指令挡住绝大部分攻击:limit_req 限频率,limit_conn 限并发,高危接口单独设限,CDN 后面记得配 realip。这套方案零成本、零依赖,上线即生效。不要一上来就追求极限参数,先按建议值部署,观察几天访问日志和限流日志,再根据真实流量微调 rate 和 burst,做到既挡攻击又不误伤正常读者,才算真正配置到位。

Last modification:August 20th, 2026 at 08:47 am

Leave a Comment