个人站点被DDoS攻击的概率远比想象中高。别以为只有大平台才会被盯上,很多针对独立博客、小型论坛、个人网店的攻击,往往源于脚本小子的随机扫描、同业恶意竞争,甚至只是你在社交群里说了一句得罪人的话。当攻击流量涌来时,你的VPS瞬间CPU满载、带宽占满,服务商发来黑洞路由的通知,站点直接瘫痪。这时候去购买动辄数万元的企业级高防服务显然不现实,我们需要的是在有限的预算下,用轻量化的方案把防御能力拉到最高。

轻量化防御的核心思路不是硬抗,而是“四两拨千斤”。你不需要一台能吞吐T级流量的清洗设备,你要做的是在攻击流量到达你的服务器进程之前,用极低的资源开销把它丢弃或转发。这要求我们把防御重心放在网络层和传输层,尽可能避免应用层参与判断,因为一旦请求进入了Nginx或Apache的工作进程,CPU和内存就已经被消耗了。

利用XDP进行内核旁路丢包

如果你用的是Linux服务器,内核版本在4.8以上,XDP(eXpress Data Path)是目前最极致的轻量级DDoS防御手段。它的原理是在网卡驱动接收到数据包的第一时间,就把包交给eBPF程序处理,这个位置比内核协议栈还要早,CPU开销极低。你可以写一段简单的eBPF程序,对UDP洪水、SYN洪水等直接进行限速和丢弃。

实际操作上,你不需要从零写C语言eBPF代码。现在有现成的工具可以帮你生成规则。比如xdp-tools套件里的xdp-filter,可以直接通过命令行对特定端口、协议做限制。假设你的站点只提供TCP 80和443端口服务,那么一条规则就能把所有非这两个端口的UDP流量在网卡层面丢弃。对于UDP反射放大攻击这种最常见的个人站点杀手,XDP能在不消耗任何CPU资源的情况下把攻击流量清零。

更进阶的做法是结合XDP和流量采样。你可以用eBPF写一个简单的计数器,对每个源IP的包速率进行统计,超过阈值的直接丢进黑名单,这个黑名单可以动态更新,不需要重启任何服务。这套机制跑在一台1核1G的轻量云服务器上,处理数百万PPS的SYN洪水毫无压力,因为数据包根本不会进入内核协议栈。

利用iptables和ipset构建智能限速

不是所有环境都能用XDP,比如你的VPS内核版本较低,或者用的是容器化部署不方便加载eBPF程序。这时候iptables配合ipset依然是性价比极高的方案。很多人对iptables的误解在于觉得它性能差,实际上性能瓶颈不在iptables本身,而在于规则数量和匹配方式。如果你用一条条线性匹配的规则,几百条之后确实会拖慢网络栈,但如果你用ipset把IP集合放在哈希表里,匹配效率是O(1),几万条黑名单对性能的影响微乎其微。

具体的策略可以这样设计:创建一个名为blacklist的ipset,超时时间设为24小时。然后用iptables的recent模块做动态限速,对每个源IP的新建连接速率做限制。比如个人博客站点,单个IP每秒新建连接数超过10个就判定为异常,直接加入blacklist。这个阈值需要根据你的站点实际访问量调整,一个日访问几百人的个人博客,正常用户不可能每秒发起10个新连接。

下面这段脚本可以直接在你的服务器上运行,它会创建一个基于哈希的IP集合,并在iptables中加入动态黑名单规则:

# 创建ipset集合,支持超时自动清除
ipset create blacklist hash:ip timeout 86400

# 在iptables中配置动态限速和黑名单
iptables -N DDoS_CHECK
iptables -A DDoS_CHECK -m recent --name ddos --set
iptables -A DDoS_CHECK -m recent --name ddos --rcheck --seconds 1 --hitcount 10 -j SET --add-set blacklist src
iptables -A DDoS_CHECK -j RETURN

# 在INPUT链中调用
iptables -I INPUT -p tcp --dport 80 -m state --state NEW -j DDoS_CHECK
iptables -I INPUT -p tcp --dport 443 -m state --state NEW -j DDoS_CHECK
iptables -I INPUT -m set --match-set blacklist src -j DROP

这套规则的逻辑很清晰:每个新到的TCP连接请求,先在recent模块里记录并检查,1秒内超过10次命中就写入ipset黑名单,后续所有来自该IP的包直接丢弃。黑名单24小时后自动过期,防止误伤动态IP的正常用户。整个过程在内核态完成,效率远高于在应用层做判断。

用Nginx做应用层最后防线

网络层的防御能挡掉99%的流量型攻击,但有些CC攻击伪装成正常的HTTP请求,速率不高,每个请求都完整建立TCP连接,甚至完成TLS握手。这类攻击会穿透iptables的限速规则,最终消耗你的PHP-FPM或后端服务资源。这时候需要在Nginx层面做最后一层过滤。

Nginx的limit_req和limit_conn模块是标配,但配置方式有讲究。很多人直接对全站做统一限速,结果把搜索引擎爬虫和正常用户一起限了。正确的做法是做分级限速:对静态资源只做连接数限制,不做请求速率限制;对动态请求按URL路径分别设置合理的阈值。比如你的WordPress站点,/wp-login.php这个登录接口应该是限速最严格的,正常用户一分钟访问一次就够了,而文章页面的访问频率可以放宽。

还有一个容易被忽视的点是Nginx的worker_connections参数。攻击发生时,大量的空闲连接会占满worker_connections,导致正常请求无法建立连接。你需要主动降低keepalive_timeout的值,攻击期间可以临时设为10秒甚至更低,让僵尸连接快速释放。同时开启Nginx的status监控页面,实时观察当前活跃连接数,一旦异常可以马上调整参数。

利用CDN的免费防护能力

个人站点最经济的做法是把域名解析托管到支持DDoS防护的CDN服务商。目前国内多家CDN厂商对个人用户提供免费的防护额度,通常能抵御10Gbps到50Gbps不等的攻击流量。这个量级对于绝大多数个人站点来说足够了。你需要做的就是把域名的NS记录或CNAME指向CDN,并在CDN控制台开启基础防护策略。

配置上有几个关键点。第一,源站IP绝对不能暴露。很多人在站点上线初期没有套CDN,搜索引擎已经收录了源站IP,攻击者通过历史DNS记录或直接扫描IP段就能绕过CDN。解决方法是更换源站IP,并在服务器防火墙上只允许CDN的回源IP访问80和443端口,其他所有IP的请求一律拒绝。第二,CDN的缓存策略要配置得当,静态资源尽可能长时间缓存,减少回源请求。攻击流量打到CDN节点上,如果命中缓存,CDN自己就能处理,不会对源站产生任何压力。

如果你的站点对HTTPS有强制要求,建议在CDN上开启TLS 1.3并配置最低限度的加密套件。TLS握手本身也是计算开销,攻击者发起大量TLS握手请求同样能消耗服务器资源。把TLS终止在CDN边缘节点,源站和CDN之间可以用自签名证书通信,这样即使攻击者绕过了网络层防御,TLS握手的计算压力也由CDN承担。

实时监控和自动化响应

防御策略部署完之后,监控和自动化响应是决定实际效果的关键。你不可能24小时盯着服务器看,攻击往往发生在凌晨。你需要一套轻量级的监控脚本,在检测到异常流量时自动执行预设的防御动作。

一个实用的方案是用Shell脚本定时采样网络流量数据。比如每10秒统计一次80和443端口的连接数,当连接数超过预设阈值时,自动调高iptables限速的敏感度,或者临时开启SYN Cookie。下面是一个简单的监控脚本框架:

#!/bin/bash
THRESHOLD=500
while true; do
    CONN_COUNT=$(ss -tan state established '( sport = :80 or sport = :443 )' | wc -l)
    if [ "$CONN_COUNT" -gt "$THRESHOLD" ]; then
        echo "$(date): 检测到异常连接数 $CONN_COUNT,启动应急策略"
        # 临时降低Nginx keepalive超时
        # 或启用更严格的iptables规则
        sysctl -w net.ipv4.tcp_syncookies=1
        sysctl -w net.ipv4.tcp_max_syn_backlog=2048
    fi
    sleep 10
done

这个脚本可以根据你的实际需求扩展,比如接入企业微信或钉钉的机器人推送告警消息,让你在第一时间知道站点正在遭受攻击。自动化响应的原则是宁可误杀不可漏过,因为个人站点的正常用户量有限,临时提高防御等级对用户体验的影响远小于站点完全瘫痪。

服务器内核参数调优

很多个人站点的服务器用的是发行版默认的内核参数,这些默认值是为通用场景设计的,面对DDoS攻击时表现很差。几个关键参数必须调整。net.ipv4.tcp_syncookies要设为1,开启SYN Cookie机制,防止SYN半连接队列被占满。net.ipv4.tcp_max_syn_backlog适当调大,给合法用户留出排队空间。net.ipv4.tcp_synack_retries减小到1或2,让服务器更快地放弃无响应的半连接。

net.core.netdev_max_backlog这个参数控制网卡接收队列的最大长度,默认通常是1000,建议调大到5000以上。当网卡收到数据包的速度超过内核处理速度时,包会在这个队列里排队,队列满了之后新到的包直接丢弃。攻击期间这个队列会迅速被占满,增大队列长度能给XDP或iptables争取更多的处理时间。

还有一个容易被忽略的参数是net.netfilter.nf_conntrack_max,它控制连接跟踪表的最大条目数。DDoS攻击会产生大量连接记录,一旦连接跟踪表满了,新的合法连接也会被拒绝。你可以通过dmesg命令检查是否有"nf_conntrack: table full"的日志,如果有,就需要调大这个值,同时降低连接跟踪的超时时间。

轻量化方案的边界和取舍

必须坦诚地说,这套轻量化方案有它的能力边界。它无法防御带宽耗尽型攻击,如果你的服务器带宽只有10Mbps,攻击者直接灌入100Mbps的垃圾流量,你的服务器网卡在物理层面就被打满了,任何软件层面的防御都无能为力。这种情况下唯一的解是把域名解析切到高防IP或者CDN,让上游网络去承载带宽压力。

另外,个人站点在防御上要做取舍。你不可能像企业一样做异地多活、Anycast流量调度。接受一定程度的可用性降级是理性的选择。比如攻击期间暂时关闭搜索功能、减少动态内容渲染、把页面降级为静态HTML,这些临时措施能让你的站点在攻击下保持基本的可访问性,而不是彻底白屏。

最后,日常做好数据备份和快照,确保即使服务器被攻击到需要重装系统,你的内容数据不会丢失。防御DDoS的最终底线不是保证服务永远在线,而是保证你的数字资产安全可控。把上面的方案组合起来,根据你的实际环境调整参数,一台几百块一年的轻量云服务器,完全有能力在绝大多数攻击场景下存活下来。