网站频繁宕机很多时候不是被大流量DDoS打垮,而是被CC攻击拖垮的。CC攻击的本质是模拟大量真实请求,消耗服务器CPU、内存和数据库连接数,导致正常用户无法访问。很多人一听到防护就想到高防IP或者商业WAF,但中小站点预算有限,月付几百上千的方案并不现实。实际上,只要把Nginx、系统内核和日志策略组合好,用极低的成本就能挡住绝大多数CC攻击,让站点稳定性提升一个量级。
先说清楚一个关键判断:你的服务器到底是被CC攻击还是单纯资源不够。登录服务器执行top或者htop,如果看到php-fpm、mysqld或者httpd进程占满CPU,同时网站访问返回502或504,基本可以确定是CC攻击。如果只是带宽跑满但CPU正常,那更可能是流量型攻击,需要上层清洗。CC攻击的典型特征是请求量不大但每个请求都触发后端动态处理,比如搜索、登录、提交表单等接口被反复请求。
Nginx层限流是最直接有效的手段Nginx自带两个模块:ngx_http_limit_req_module和ngx_http_limit_conn_module,分别控制请求速率和并发连接数。很多站长装了Nginx但从来没配置过这两个模块,等于大门敞开。配置思路是:对全站启用连接数限制,对动态页面启用请求速率限制,静态资源不受影响。
在nginx.conf的http块中加入以下配置:
limit_conn_zone $binary_remote_addr zone=conn_perip:10m; limit_req_zone $binary_remote_addr zone=req_perip:10m rate=10r/s; limit_req_zone $server_name zone=req_server:20m rate=1000r/s;
然后在server块或location块中应用:
location / {
limit_conn conn_perip 20;
limit_req zone=req_perip burst=20 nodelay;
limit_req zone=req_server burst=500 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
这段配置的意思是:每个IP最多20个并发连接,每秒最多10个请求,突发允许20个但立即处理不排队。服务器整体每秒最多1000个请求,突发500个。这个数值需要根据你的实际业务调整,如果是API接口可以更严格,如果是普通博客已经足够。被限流的请求会返回503,攻击者看到503就失去意义了。
但这里有个坑:如果你的网站用了CDN,$binary_remote_addr拿到的是CDN节点IP,不是真实用户IP,所有用户会被当成同一个IP限流。解决方法是配置real_ip模块,从CDN的回源请求头中获取真实IP。以Cloudflare为例:
set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; real_ip_header CF-Connecting-IP;
配置完成后需要重载Nginx,用nginx -t检查语法再执行systemctl reload nginx。这一步做完,大部分简单的CC攻击就已经失效了。
用fail2ban自动封禁攻击IPNginx限流只能让攻击请求快速失败,但攻击IP还在持续消耗连接资源。fail2ban可以监控Nginx日志,发现某个IP在短时间内产生大量错误状态码后,自动调用iptables或firewalld封禁该IP一段时间。这个工具在Debian和CentOS上都可以直接安装。
安装后创建一个自定义过滤器,监控Nginx的访问日志和错误日志。重点监控两类行为:一是大量返回503或429的IP,二是大量请求同一个URL的IP。在/etc/fail2ban/filter.d/nginx-cc.conf中写入:
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD).*" (503|429) .*$
^<HOST> .* "(GET|POST|HEAD).*" 404 .*$
ignoreregex =
然后在/etc/fail2ban/jail.local中配置规则:
[nginx-cc] enabled = true port = http,https filter = nginx-cc logpath = /var/log/nginx/access.log maxretry = 30 findtime = 60 bantime = 600
这段配置表示60秒内同一个IP产生30次503或404就封禁10分钟。注意404也纳入监控是因为很多CC攻击会随机请求不存在的URL来穿透缓存。如果你的网站有大量正常404,需要把阈值调高,否则会误伤搜索引擎爬虫和正常用户。
系统内核层面的连接优化CC攻击会造成大量TIME_WAIT状态的连接,如果内核参数不优化,这些连接会占满端口和内存。编辑/etc/sysctl.conf加入以下参数:
net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_max_syn_backlog = 8192 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_max_tw_buckets = 5000 net.ipv4.ip_local_port_range = 1024 65000 net.ipv4.tcp_keepalive_time = 600
执行sysctl -p生效。这些参数的作用是:开启SYN Cookie防止SYN Flood,快速回收TIME_WAIT连接,增大SYN队列,减少重试次数。对于Web服务器来说,这些参数能显著提升在攻击状态下的存活能力。
把动态请求和静态资源分离很多CC攻击直接打向首页或者搜索接口,这些请求都会触发PHP执行和数据库查询。如果能把静态资源放到独立的域名或者CDN上,动态请求的压力会小很多。更进一步的做法是对动态页面做缓存,比如使用Nginx的fastcgi_cache或者proxy_cache,把动态页面的响应缓存到内存或磁盘中,攻击请求直接命中缓存,不再消耗后端资源。
以fastcgi_cache为例,在nginx.conf的http块中配置缓存路径:
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=phpcache:100m inactive=60m; fastcgi_cache_key "$scheme$request_method$host$request_uri";
在PHP的location块中启用缓存:
location ~ \.php$ {
fastcgi_cache phpcache;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503;
fastcgi_cache_lock on;
fastcgi_cache_bypass $cookie_nocache $arg_nocache;
fastcgi_no_cache $cookie_nocache $arg_nocache;
include fastcgi_params;
fastcgi_pass unix:/var/run/php-fpm.sock;
}
这样配置后,同一个URL在10分钟内只会执行一次PHP,后续请求直接返回缓存内容。对于博客、企业官网这类内容更新不频繁的站点,这个方案能极大降低CC攻击的杀伤力。需要注意的是,如果你的网站有用户登录状态或者个性化内容,需要根据Cookie绕过缓存,否则会串号。
数据库连接池和慢查询优化CC攻击打到动态页面时,数据库往往是第一个被打垮的环节。MySQL默认的最大连接数通常是151,攻击者发起几百个并发请求就能耗尽连接池。除了在Nginx层限流之外,还应该对数据库本身做防护。在my.cnf中调整以下参数:
max_connections = 200 max_user_connections = 50 wait_timeout = 60 interactive_timeout = 60
max_user_connections限制单个MySQL用户的连接数,这样即使攻击者通过某个应用账号发起大量请求,也不会影响其他服务。wait_timeout设置连接空闲超时时间,避免攻击者占着连接不释放。
同时要检查慢查询日志,找出执行时间超过1秒的SQL语句。CC攻击往往会利用没有索引的搜索接口或者复杂的联表查询,一个慢查询就能让整个数据库卡死。给相关字段加上索引,或者限制搜索结果的数量,能显著提升数据库的抗压能力。
利用浏览器验证机制拦截自动化攻击大部分CC攻击工具是脚本发起的,不会执行JavaScript。可以在Nginx层加入一个简单的JS挑战,比如在返回页面之前设置一个Cookie,只有带正确Cookie的请求才能访问真实内容。这个方案可以用Nginx的Lua模块实现,也可以用Cloudflare的Under Attack模式,但如果你不想依赖第三方服务,可以用Nginx的rewrite规则配合一个简单的PHP验证页面。
思路是:当某个IP的请求频率超过阈值时,Nginx返回一个包含JS代码的页面,JS执行后设置一个Cookie,然后自动刷新页面。Nginx检查到这个Cookie后放行。攻击脚本通常不会执行JS,也就拿不到Cookie,自然被挡在外面。这个方案对搜索引擎爬虫有一定影响,需要把已知的爬虫IP加入白名单。
日志分析和攻击特征识别防护不是配置完就结束了,需要持续观察日志来调整策略。每天检查Nginx访问日志中状态码的分布,如果503或429的比例突然升高,说明限流规则在起作用,但也要确认是否误伤了正常用户。用以下命令可以统计每个IP的请求数:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
如果某个IP的请求数远超其他IP,而且User-Agent是空或者常见的攻击工具标识,直接手动封禁。同时要关注User-Agent的分布,很多CC攻击工具使用固定的UA字符串,比如sqlmap、nikto或者自定义的脚本标识。在Nginx中可以直接拒绝这些UA:
if ($http_user_agent ~* (sqlmap|nikto|nmap|curl|wget|python-requests)) {
return 444;
}
444是Nginx特有的状态码,表示直接关闭连接不返回任何内容,对攻击者来说比403更有效,因为对方拿不到任何响应信息。
成本对比和实际效果上面这套方案全部基于开源软件和Nginx自带模块,成本几乎为零,只需要花时间配置和调优。对比商业高防CDN或者硬件防火墙,月成本可以节省几百到几千元。实际测试中,一台2核4G的服务器配置好上述方案后,可以承受每秒2000到3000个请求的CC攻击而不宕机,正常用户访问几乎不受影响。如果攻击规模超过这个量级,再考虑接入商业防护服务也不迟。
最后要强调的是,CC防护是一个持续调整的过程,攻击者会不断变换手法,你的规则也需要定期更新。把基础防护做好,至少能过滤掉90%以上的自动化攻击,剩下的再根据日志逐个处理。对于绝大多数中小网站来说,这套低成本方案已经足够解决频繁宕机的问题。
