
前言:一个让我夜不能寐的问题
上周五凌晨3点,我被短信惊醒——服务器CPU飙到100%,网站全挂。
查日志,触目惊心:
# 1小时内的恶意请求
grep "nikto\|sqlmap\|nmap" access.log | wc -l
# 结果:23847
2万多次攻击扫描,来自全球各地的IP,疯狂探测我的漏洞。
这不是个例。根据 Cloudflare 2025年报告,一个暴露在公网的服务器,平均每天会收到超过10万次恶意请求。
于是我花了整整一周,搭建了这套5层纵深防御体系。
现在,我的服务器稳如老狗:
# 过去7天拦截统计
[L1 内核层] SYN Flood: 12,847 次
[L2 Fail2Ban] 封禁IP: 2,341 个
[L3 Lua拦截] 恶意UA: 45,892 次
[L4 限流] 429返回: 8,234 次
[L5 WAF] 攻击拦截: 15,678 次
今天,我把这套方案完整开源。
[写在前面] 如果你是个人开发者、独立站长,或是需要自己管理服务器的技术爱好者,这篇文章就是为你量身打造的。我们不讲虚的,直接上代码,保证能跑。
一、架构全景:5层纵深防御
先看整体架构,理解每一层的职责:

核心设计原则:
| 层级 | 位置 | 拦截目标 | 性能消耗 | 技术栈 |
|---|---|---|---|---|
| L1 | 内核 | SYN Flood、ICMP 洪水 | 极低 | sysctl + firewalld |
| L2 | 宿主机 | 协议探测、端口扫描、暴力破解 | 低 | Fail2Ban + IPSet |
| L3 | 容器 | 空UA、爬虫脚本、工具指纹 | 低 | OpenResty Lua |
| L4 | 容器 | 高频请求 (CC攻击) | 中 | Nginx limit_req |
| L5 | 容器 | SQL注入、XSS、CVE漏洞 | 高 | ModSecurity + CRS |
越靠前拦截,性能消耗越低。 这就是纵深防御的精髓。
二、L1 内核层:第一道防线
SYN Flood 是最常见的 DDoS 攻击方式。攻击者发送大量半连接请求,耗尽服务器资源。
解决方案:sysctl 内核参数调优 + firewalld 限速
2.1 sysctl 配置
# 创建配置文件
sudo bash -c 'cat > /etc/sysctl.d/99-anti-ddos.conf << EOF
# ========================================
# SYN Flood 防护配置
# 作者: wwj
# ========================================# 【必须】启用 SYN Cookie
# 原理: 不保存半连接状态,用加密cookie验证
net.ipv4.tcp_syncookies = 1# 【优化】减少 SYN 重试次数 (默认6)
# 效果: 更快释放无效连接
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2# 【优化】增加 SYN 队列长度 (默认128)
# 效果: 容纳更多正常连接
net.ipv4.tcp_max_syn_backlog = 4096# 【优化】TIME_WAIT 复用
# 效果: 减少端口耗尽风险
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30# 【Docker必须】增大连接跟踪表
# 默认65535,高并发场景容易满
net.netfilter.nf_conntrack_max = 1048576
EOF'# 立即生效
sudo sysctl -p /etc/sysctl.d/99-anti-ddos.conf# 验证配置
sysctl net.ipv4.tcp_syncookies
# 输出应该是: net.ipv4.tcp_syncookies = 1
2.2 firewalld SYN 限速
相比 iptables,firewalld 的管理更加现代化。我们在防火墙层直接丢弃过快的连接。
# 限制每秒最多25个新连接 (突发50个)
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \-p tcp --syn -m limit --limit 25/s --limit-burst 50 -j ACCEPTsudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \-p tcp --syn -j DROP# 限制 ICMP (防 Ping Flood)
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \-p icmp -m limit --limit 10/s --limit-burst 20 -j ACCEPTsudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \-p icmp -j DROP# 重载规则
sudo firewall-cmd --reload# 验证
sudo firewall-cmd --direct --get-all-rules
效果: 99% 的 SYN Flood 攻击在内核层就被丢弃,根本不会到达你的 Nginx。
三、L2 宿主机层:Fail2Ban + IPSet 高性能封禁
普通玩家用 Fail2Ban 修改 iptables 规则,每封一个 IP 加一条规则,封几千个 IP 后系统就慢如蜗牛。
高端玩家使用 IPSet。 无论封禁 1 个还是 10 万个 IP,匹配耗时都是 O(1)(恒定时间),对 CPU 几乎零负担。
此外,为了完美配合 Docker,我们使用 journald 作为日志驱动,彻底解决容器日志轮转难题。
3.1 准备工作:配置 Docker 日志驱动
修改 docker-compose.yml,让 OpenResty 使用 journald 写日志:
services:op:image: sprinng/openresty:1.27.1.2-ms-small-alpine3.23.0container_name: oplogging:driver: journald # 关键: 使用系统日志驱动options:tag: "{{.Name}}"
修改后记得重建容器:docker-compose up -d --force-recreate
3.2 安装 Fail2Ban 和 IPSet
# CentOS/AlmaLinux
sudo dnf install -y epel-release
sudo dnf install -y fail2ban ipset# 启动服务
sudo systemctl enable --now fail2ban
3.3 配置 4 个金牌监狱
针对不同类型的攻击,我们设计了 4 个专用的“监狱”:
- nginx-bad-request: 专抓协议探测(400错误)
- nginx-path-scan: 专抓目录扫描(404错误)
- nginx-path-scan-crawler: 爬虫专用通道(容忍度更高)
- nginx-auth-bruteforce: 专抓暴力破解(401/403错误)
一键创建配置:
sudo bash -c 'cat > /etc/fail2ban/jail.d/docker-nginx.local << EOF
[DEFAULT]
# 白名单 (Cloudflare IP + 本地IP)
ignoreip = 127.0.0.1/8 ::1 172.18.0.0/16 192.168.0.0/16 10.0.0.0/8 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22# 核心: 使用 ipset 高性能封禁
banaction = iptables-ipset-proto6# 监狱1: 协议探测 (1次400即封)
[nginx-bad-request]
enabled = true
backend = systemd
journalmatch = CONTAINER_NAME=op
filter = nginx-bad-request
maxretry = 1
findtime = 60
bantime = 604800 # 封7天# 监狱2: 路径扫描 (60秒20次404)
[nginx-path-scan]
enabled = true
backend = systemd
journalmatch = CONTAINER_NAME=op
filter = nginx-path-scan
maxretry = 20
findtime = 60
bantime = 3600# 监狱3: 爬虫容错通道
[nginx-path-scan-crawler]
enabled = true
backend = systemd
journalmatch = CONTAINER_NAME=op
filter = nginx-path-scan-crawler
maxretry = 200
findtime = 60
bantime = 3600# 监狱4: 暴力破解 (10次401/403)
[nginx-auth-bruteforce]
enabled = true
backend = systemd
journalmatch = CONTAINER_NAME=op
filter = nginx-auth-bruteforce
maxretry = 10
findtime = 60
bantime = 21600
EOF'
注:对应的 filter 配置文件较长,因篇幅限制未完全列出,文末部署包中包含完整配置。
四、L3 容器 Lua 层:高性能 UA 拦截
为什么用 Lua 而不是 nginx if?
因为 Lua 灵活、性能高,且可以打印详细日志,方便排查是谁在攻击你。
-- block_rules.lua 片段
-- 规则: 拦截常见黑客工具和脚本local ua_blacklist = {{"^curl/", "curl"},{"^Wget/", "wget"},{"^python%-requests", "python-requests"},{"^Go%-http%-client", "go-http-client"},{"^Scrapy", "scrapy"},-- ... 更多规则
}local ua = ngx.var.http_user_agent or ""
for _, rule in ipairs(ua_blacklist) doif string.match(string.lower(ua), rule[1]) thenngx.log(ngx.WARN, "[BLOCK] reason=ua_blacklist, tool=", rule[2], ", ip=", ngx.var.remote_addr)return ngx.exit(444) -- 444 直接断开连接,不给任何回显end
end
效果: 绝大多数脚本小子(Script Kiddies)在这一层就会被无情断开,连 HTTP 头都拿不到。
五、L4 容器限流层:nginx limit_req
CC 攻击的特点是:高频请求,消耗服务器资源。
nginx 的 limit_req 是最高效的解决方案。
5.1 核心配置
# http 块中定义 zone
# 关键: 使用 $binary_remote_addr$host 作为 key
# 同一 IP 访问不同域名,独立计数
limit_req_zone $binary_remote_addr$host zone=domain_req_limit:10m rate=60r/s;# server 块中使用
limit_req zone=domain_req_limit burst=100 nodelay;
limit_req_status 429;
参数详解:
| 参数 | 含义 |
|---|---|
$binary_remote_addr$host |
按 IP+域名 独立计数 |
10m |
10MB 内存,约存储 16 万个 key |
rate=60r/s |
每秒最多 60 个请求 |
burst=100 |
允许突发 100 个请求 |
nodelay |
突发请求不排队,直接处理或拒绝 |
为了更形象地理解,我们可以把它看作一个“漏桶”,如下图所示:

5.2 为什么用 IP+域名 作为 key?
假设你有多个域名:a.com、b.com、c.com
如果只用 IP 作为 key:
- 用户访问 a.com 30次 + b.com 30次 + c.com 30次
- 总计 90 次,触发限流,用户体验差
使用 IP+域名 作为 key:
- 每个域名独立计数
- 用户每个域名都可以访问 60 次
- 不会误伤正常用户
六、L5 容器 WAF 层:ModSecurity + OWASP CRS
这是最后一道防线,也是最智能的防线。它能识别 SQL 注入、XSS、WebShell 上传等复杂攻击。
6.1 核心配置避坑
新手最容易踩的坑: Fail2Ban 启动失败。
原因往往是 ModSecurity 的审计日志文件不存在。Fail2Ban 启动时必须检测到日志文件,否则会报错退出。
解决方案(Pro Tip):
在部署前,务必手动创建日志文件:
# 确保目录和文件存在
sudo mkdir -p /data/op/modsecurity/logs/modsecurity/
sudo touch /data/op/modsecurity/logs/modsecurity/audit.log# 然后再启动容器和 Fail2Ban
6.2 启用 OWASP CRS
我们使用 OWASP Core Rule Set (CRS) v4,这是目前世界上最优秀的开源 WAF 规则集。
# modsecurity.conf
SecRuleEngine On # 开启拦截模式
Include /etc/modsecurity/coreruleset/crs-setup.conf
Include /etc/modsecurity/coreruleset/rules/*.conf
七、快速验证:你的盾牌坚固吗?
部署完成后,别忘了测试一下:
# 1. 测试 Lua 拦截 (模拟 Curl)
curl http://your-domain.com/
# 预期: 连接被立即关闭 (Empty reply from server)# 2. 测试 WAF 拦截 (模拟 SQL 注入)
curl "http://your-domain.com/?id=1' OR '1'='1"
# 预期: 403 Forbidden# 3. 测试 Fail2Ban (模拟扫描)
# 在另一台机器连续请求不存在的页面
for i in {1..30}; do curl http://your-domain.com/scan_$i; done
# 预期: IP 被封禁,连接超时
八、为什么你需要“生产级安全部署包”?
上面展示的是核心配置,足以应对 90% 的普通攻击。但从“能用”到“生产级好用”,还有很长的路要走:
- Fail2Ban 的 filter 正则很难写:写错一个字符,Fail2Ban 就无法识别日志,防护失效。
- ModSecurity 误报多:WordPress 后台无法保存?富文本编辑器无法提交?不处理误报,网站根本没法用。
- 规则库更新麻烦:攻击手段日新月异,你的规则库还在用去年的吗?
- IPSet 配置复杂:需要精确的 ipset 命令配合 iptables 才能生效,错一步就是全网断连。
为了让你少走弯路,我将这套经过半年实战验证的方案打包好了。