为了防范潜在的 DDOS 攻击,避免经济损失,我在近期升级了本博客的服务器。更新后,服务器实装了 Nginx 自建的 HTTP 限流模块,配合 fail2ban 封杀恶意 IP,可以有效抵御大量并发请求对服务器的干扰。
Nginx 部分
在 Nginx 上配置 HTTP 限流模块,分为两个步骤:
- 创建一个管理区域(以下称 zone)
- 将 zone 应用于指定的文件路径(location)
我曾在文章 本博客现已启用全站 HTTPS 加密通讯 中贴出了配置文件 nginx.conf
的代码片段。简明起见,那篇博客省略了配置文件的根节点。实际上,整个文件的结构是这样子的:
1 | 进程设定 |
Nginx 的每个 zone 记录了一套管理规则。这个规则会在开启了该 zone 的 location 上生效。也就是说,应在“其它设定”下面声明 zone,并在“文件路径映射”那里引用它。
来看两个 zone 的声明的例子。
1 | limit_req_zone $binary_remote_addr zone=perip:10m rate=30r/m; |
第一个例子中,limit_req_zone
表明这条语句声明一个限流 zone。$binary_remote_addr
是 Nginx 内置的变量,用来表示客户端的 IP 地址。它决定了这个 zone 会以客户端 IP 作为控制条件。perip
是 zone 的名字。冒号后的 10m
表示这个 zone 最大可占用的内存空间。zone 要存储包括 IP 地址在内的客户端状态信息。根据 官方文档,状态信息在 32 位机上占用 64 字节,而在 64 位机上占用 128 字节。对我的 64 位服务器而言,10 MB 的内存理论上支持 8 万个并发连接,是绰绰有余的。最后 30r/m
规定,每个 IP 地址的平均请求速度不得大于每分钟 30 次。因为不支持小数的缘故,这里不能写作 0.5r/s
。
1 | limit_req_zone $server_name zone=perserver:10m rate=100r/s; |
第二个例子使用了内置变量 $server_name
,使得这个 zone 作用于 Nginx 自己。它的意思是,当某个逻辑服务器的平均请求速度大于每秒 100 次时,将不再受理新的请求。
在 location 内引用一个 zone,请参考下面的例子。
1 | limit_req zone=perip burst=5 nodelay; |
这条指令为当前的 location 启用 perip
zone。尽管 perip
限制了平均请求速度,Nginx 依旧允许客户端并发创建 5 个连接,以适应现代浏览器的需要。过载后,开启了 nodelay
的 Nginx 将立即向客户端返回错误码。如果没有 nodelay
选项,Nginx 会故意延迟响应客户端的请求,以便将其响应请求的平均速度拉低到限定值以下。因为在这种情况下,服务器仍然需要消耗内存记录尚未响应的请求,所以对付 DDOS 攻击一定要开启 nodelay
选项。
在声明 zone 的位置,同时可以自定义返回的状态码。默认返回的状态码是 503 服务暂时不可用。我建议通过 limit_req_status 444;
将它替换成 444 无回应断开连接。444 是一个由 Nginx 引入的非标准 HTTP 状态码,它从字面上将拒绝服务的责任从服务器端 (5xx) 转移到客户端 (4xx),更加真实地反映了客观情况。
如果 Nginx 服务器流量超限,我们可以在 error 日志中找到这样的记录:
1 | ... limiting requests, excess:..., client:..., server:..., request:..., host:... |
Nginx 的限流仅仅可以停止响应服务,但客户端依然能不断地发送 TCP 请求建立新的连接。这时需要 fail2ban 上场彻底阻断恶意客户端的魔爪。fail2ban 的工作,就是从监控日志开始的。
fail2ban 部分
fail2ban 可在各 Linux 发行版的包管理器中安装。它的过滤规则位于 /etc/fail2ban/filter.d/
路径下。新安装的 fail2ban 已经设置好了多项规则,我们只要照着模板完成 nginx-http-limit-req.conf
文件即可(文件名任取)。
1 | nginx-http-limit-req.conf |
failregex
描述了 fail2ban 期望匹配的特征。.*
用于匹配中间的任意多个字符。重点是 client: <HOST>
,fail2ban 依此来抓取客户端的 IP 地址。
之后,需要注册并启用这条规则。因为原始的配置文件 /etc/fail2ban/jail.conf
会随着版本更新而被覆盖,我们要创建一个副本 /etc/fail2ban/jail.local
,并在其中填写配置。在文件的最后插入下面这段内容:
1 | [nginx-http-limit-req] |
注意,标签要与过滤规则的文件名相同。在默认状态,所有规则都是禁用的,我们需要单独启用想要加载的规则。fail2ban 运行后,会自动监测 %(nginx_error_log)s
文件,如果在 600 秒的时间内(findtime
)连续 5 次(maxretry
)发现客户端的请求速度超过限额,则在将来的 7200 秒内(bantime
)禁止该 IP 到服务器 http 和 https 端口的连接。findtime
和 maxretry
都使用了默认值,因此那两行也可以不写。
一切就绪后,运行 sudo service fail2ban restart
重载 fail2ban。日志 /var/log/fail2ban.log
中记录了攻击者的 IP 地址。可惜它们通常是肉鸡,不值得打回去。总而言之,咱们服务器管理员要牢记的一点是:网络世界并不太平。