云计算百科
云计算领域专业知识百科平台

凌晨3点服务器被CPU打爆!从裸奔到铜墙铁壁,这套纵深防御方案救了我的命

前言:一个让我夜不能寐的问题

上周五凌晨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 ACCEPT

sudo 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 ACCEPT

sudo 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.0
container_name: op
logging:
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) do
    if string.match(string.lower(ua), rule[1]) then
    ngx.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 才能生效,错一步就是全网断连。

  • 如有需求,请关注公众号 [技术修罗] ,获取

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 凌晨3点服务器被CPU打爆!从裸奔到铜墙铁壁,这套纵深防御方案救了我的命
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!