小型站点运维最怕的不是技术难题,而是资源错配。一台1核2G的轻量云服务器,跑着网站程序、数据库,可能还挂着Redis,本身内存和CPU就吃紧。这时候如果遭遇CC攻击,流量还没到服务器带宽极限,服务器的处理线程和数据库连接池先被占满了,网站直接502。传统的CC防护策略,比如强制验证码、JS跳转、浏览器指纹验证,确实有效,但往往需要独立的WAF设备、高防IP或者复杂的规则引擎。这些对于小型站点来说,一是成本太高,二是维护复杂。轻量化CC防护的核心思路,不是硬扛,而是用极低的资源开销,在请求到达业务逻辑之前,把恶意流量剥离。
理解CC攻击的“七寸”CC攻击之所以难防,在于它模拟的是正常用户的HTTP请求。攻击者利用代理IP池,发起大量对动态页面(比如搜索页、提交页、数据库查询接口)的请求。这些请求每一个都会消耗服务器后端资源。轻量化防护的关键,不是去识别每一个IP是不是代理(这是高防IP该干的事),而是识别请求行为的“非人性化”特征。真正的用户访问,会有停顿、会有浏览器解析、会加载静态资源、会有Cookie携带。而绝大多数CC攻击脚本,尤其是基于Python requests、curl循环的低成本攻击,行为模式非常单一:只请求目标URL,不解析JS,不加载CSS/图片,不携带正常的浏览器Cookie,且请求间隔极其规律。抓住这几点,就能用很轻量的手段过滤掉90%以上的攻击流量。
第一道防线:Web服务器层面的连接控制不要一上来就想着装各种复杂的防护软件。先把Web服务器自身的并发和请求限制能力用起来。以Nginx为例,两个模块就能构建第一道轻量防线:ngx_http_limit_conn_module 和 ngx_http_limit_req_module。limit_conn限制的是并发连接数,基于IP地址进行限制。一个正常浏览器访问网站,通常会建立2-4个并发连接来加载页面资源。而攻击脚本往往会建立大量连接。你可以设置每个IP最大并发连接数为10,超过的直接返回503。limit_req限制的是请求速率,基于漏桶算法。设置一个合理的请求速率,比如每秒10个请求,对于正常浏览足够,对于CC攻击则远远不够。突发流量可以设置一个burst值,比如20,允许短时间内的突发请求,但超出部分会被延迟处理或直接拒绝。这两个模块工作在Nginx的HTTP处理早期阶段,资源消耗极低,几乎不影响服务器性能。配置示例如下:
http {
# 定义请求限制区域,10m内存空间,每秒10个请求
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;
# 定义连接限制区域,10m内存空间
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
server {
location / {
# 应用请求限制,burst=20,超出部分不延迟直接拒绝
limit_req zone=req_limit_per_ip burst=20 nodelay;
# 应用连接限制,每个IP最大10个并发连接
limit_conn conn_limit_per_ip 10;
}
}
}
这里有个关键点:使用$binary_remote_addr而不是$remote_addr,前者存储一个IP地址只需要4到8个字节,后者需要几十个字节,能大幅节省共享内存空间。10m的空间大约能存储16万个IP地址的限制状态,对于小型站点绰绰有余。
第二道防线:无害的静态化挑战很多CC攻击脚本根本不解析HTML,更不会执行JavaScript。利用这一点,可以在请求到达动态页面之前,插入一段极轻量的JavaScript跳转验证。这个思路和传统的JS挑战类似,但更轻量,不需要引入复杂的第三方服务。具体做法是:在Nginx层面,通过判断请求是否携带特定的Cookie来决定是放行还是返回一个包含JS跳转的页面。如果请求没有携带这个Cookie,Nginx返回一个非常简单的HTML页面,页面中只有一段JS代码,设置这个Cookie并刷新页面。正常浏览器会自动执行这段JS,携带上Cookie再次发起请求,这次Nginx检测到Cookie存在,放行请求到后端。而CC攻击脚本由于不解析JS,无法完成Cookie设置,请求就被挡在了Nginx层面,根本不会到达后端的PHP/Java/Node.js进程。这个验证过程对用户几乎无感,只会产生一次极快的重定向。Nginx配置逻辑如下:
server {
set $challenge_cookie "cc_challenge=1";
location / {
# 如果请求没有携带验证Cookie,则返回JS挑战页面
if ($http_cookie !~* "cc_challenge=1") {
add_header Set-Cookie "cc_challenge=1; path=/; max-age=3600";
return 200 '正在验证浏览器,请稍候...';
}
# 验证通过,正常处理请求
proxy_pass http://backend;
}
}
这个方案的精妙之处在于,它完全不依赖后端语言,全部在Nginx层面完成,性能开销几乎可以忽略不计。而且生成的HTML页面极小,只有几百字节,不会占用带宽。max-age设置Cookie有效期,可以避免正常用户每次访问都被挑战。对于搜索引擎爬虫,可以通过User-Agent白名单放行,避免影响SEO。主流的搜索引擎爬虫都支持解析JavaScript,但为了稳妥起见,对百度蜘蛛、必应蜘蛛等直接放行是最佳实践。
第三道防线:请求指纹的轻量级过滤真正的浏览器发起的请求,其HTTP头信息是有明显特征的。攻击脚本构造的请求,往往缺失很多浏览器会自动携带的头信息,或者头信息的顺序、内容与真实浏览器不符。在Nginx中,可以通过检查请求头的完整性来过滤掉大部分脚本攻击。一个真实的Chrome浏览器请求,通常会携带Accept、Accept-Language、Accept-Encoding、User-Agent、Connection等头,而且Accept-Language会包含具体的语言代码,Accept-Encoding会包含gzip等压缩方式。很多简单的CC脚本,只设置了一个User-Agent,甚至User-Agent都是空的或者使用默认的库标识(如python-requests/2.x)。你可以在Nginx中配置规则,如果请求缺少必要的浏览器头信息,或者User-Agent包含明显的脚本库特征,直接返回444(Nginx自定义的关闭连接状态码,不返回任何内容)。配置示例如下:
server {
location / {
# 拒绝User-Agent为空的请求
if ($http_user_agent = "") {
return 444;
}
# 拒绝User-Agent包含常见脚本库特征的请求
if ($http_user_agent ~* "(python-requests|curl|wget|scrapy|java|libwww)") {
return 444;
}
# 拒绝缺少标准浏览器Accept头的请求
if ($http_accept = "") {
return 444;
}
# 拒绝Accept-Language为空的请求
if ($http_accept_language = "") {
return 444;
}
proxy_pass http://backend;
}
}
这种基于请求头完整性的过滤,误杀率极低,因为没有任何一个正常浏览器发出的请求会缺失这些基础头信息。即使有极少数用户使用了隐私插件屏蔽了某些头信息,返回444关闭连接也比返回错误页面更节省服务器资源,攻击者得不到任何反馈。
动态页面的缓存策略优化小型站点最怕CC攻击打向搜索接口、列表翻页等动态页面,因为这些页面每次请求都要查询数据库。但仔细分析就会发现,很多看似动态的页面,其实数据更新频率并不高。比如一个博客的文章列表,可能几个小时才更新一次。一个产品展示站点的商品详情,可能几天才变动一次。对于这类页面,完全可以利用Nginx的proxy_cache或者fastcgi_cache,将动态页面的响应缓存起来。设置一个合理的缓存有效期,比如5分钟甚至1小时。这样,即使有CC攻击打过来,只要第一个请求到达后端并生成了缓存,后续的攻击请求全部命中Nginx缓存,直接由Nginx返回缓存的HTML内容,后端数据库和程序完全不受影响。对于WordPress这类动态站点,结合Nginx的fastcgi_cache,配合缓存清理插件,可以做到对登录用户不缓存,对普通访客全量缓存。配置的关键在于定义缓存键值,要忽略Cookie和查询参数中的无关变量,避免缓存被绕过或产生大量无效缓存。一个基本的fastcgi_cache配置如下:
fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=MYCACHE:50m inactive=1h max_size=500m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
location ~ \.php$ {
fastcgi_cache MYCACHE;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_bypass $cookie_nocache;
fastcgi_no_cache $cookie_nocache;
fastcgi_pass backend;
}
}
这里设置了一个50m的内存区域来存储缓存索引,缓存文件存储在/tmp/nginx_cache目录。inactive=1h表示1小时未访问的缓存会被清理。fastcgi_cache_use_stale指令非常实用,当后端出现错误或超时时,Nginx会继续使用过期的缓存内容,保证站点始终可访问,这本身就是一种抗CC的韧性设计。
日志监控与自动化响应轻量化防护不代表完全不关注攻击态势。小型站点运维人员通常没有时间实时盯着日志,但可以设置一些简单的自动化脚本。比如,编写一个Shell脚本,每分钟分析一次Nginx访问日志,统计每个IP的请求次数,如果某个IP在一分钟内的请求次数超过阈值(比如200次),就自动将这个IP加入到iptables的拒绝列表,或者添加到Nginx的IP黑名单中。这个脚本可以设置一个过期时间,比如封禁10分钟后自动解封,避免误伤。这种“慢半拍”的自动化响应,对于持续性的CC攻击非常有效。脚本逻辑可以很简单:使用awk统计日志中IP的出现次数,排序后取出超过阈值的IP,然后调用iptables进行封禁。配合crontab每分钟执行一次,就能形成一个简单的自适应防护闭环。这比人工发现再处理要快得多,而且完全免费,不需要任何外部服务。
架构层面的轻量级分离如果站点被CC攻击的频率较高,可以考虑在架构上做一点微调。将网站的静态资源(图片、CSS、JS)托管到对象存储服务上,并使用CDN加速。这样,即使源站受到CC攻击,静态资源的加载不受影响,用户看到的页面可能样式丢失,但至少不会完全白屏。更重要的是,CDN和对象存储通常自带一定的DDoS/CC防护能力,能够分担一部分攻击流量。对于动态请求,可以在源站前面套一层免费的CDN服务,利用CDN的节点IP作为“替身”,隐藏源站真实IP。很多CDN服务商提供免费的套餐,对于流量不大的小型站点完全够用。CDN节点本身具备一定的连接复用和请求过滤能力,能够把大量的无效请求挡在源站之外。这相当于把最耗费资源的连接处理工作,外包给了CDN的庞大基础设施,源站只需要处理经过CDN过滤后的少量干净流量。
总结轻量化CC防护的配置思路小型站点运维不需要大而全的安全方案,需要的是精准、低开销、易维护的防护组合。Nginx的limit_conn和limit_req模块做基础速率控制,JS Cookie挑战做无感人机识别,请求头完整性检查过滤低级脚本,动态页面缓存减少数据库压力,再加上一个简单的自动化封禁脚本,这五层防护组合起来,能够在几乎不增加服务器负载的情况下,抵御绝大多数针对小型站点的CC攻击。这套方案的核心优势在于,所有组件都是Nginx和操作系统自带的功能,不需要额外安装软件,不需要付费购买服务,配置简单且效果立竿见影。对于每天几千到几万独立访客的小型站点,这套轻量化配置完全足够应对日常的安全威胁,让运维人员能够把精力集中在业务本身,而不是疲于应对攻击。
