1. 为什么是mod_evasive而不是其他模块我第一次在客户现场处理突发流量时服务器CPU直接飙到98%Apache进程数暴涨到300top里全是httpdnetstat -an | grep :80 | wc -l显示连接数突破2800。查access.log发现同一IP在1秒内发起47次GET请求路径全是/wp-login.php——典型的暴力爆破前置行为。当时第一反应是加iptables限速但很快意识到iptables工作在网络层它拦不住已经建立的TCP连接而Apache还在拼命fork子进程去响应这些恶意请求资源早被耗尽了。这时候mod_evasive的价值就凸显出来了它工作在Apache应用层能在请求进入PHP解析器之前就完成识别与拦截。它不是靠IP黑名单硬挡而是基于三个维度实时计算——单位时间请求数、单IP并发连接数、同一URI访问频次。比如配置DOSPageCount 2意味着同一个页面如/index.html在DOSPageInterval 1秒内被同一IP访问超过2次就触发临时封禁。这个逻辑比单纯“每秒超10个包就DROP”更精准因为它理解HTTP语义能区分真实用户刷新和脚本扫库。很多人误以为DDoS防护必须用商业WAF或云清洗服务其实对中小站点mod_evasive是成本最低、部署最快的首道防线。它不改变网络拓扑不引入额外延迟所有逻辑都在Apache进程内完成。我经手的56个WordPress站点中有41个在启用mod_evasive后暴力登录成功率从日均37次降到0——不是因为攻击消失了而是攻击请求根本没机会触达PHP层就被403 Forbidden拦截了。它的核心优势在于“快准狠”快——毫秒级响应准——基于HTTP会话上下文判断狠——直接返回403并记录日志不给攻击者任何反馈。提示mod_evasive不是万能盾牌。它对SYN Flood这类网络层攻击无效也无法防御CC攻击中模拟真人行为的慢速攻击如Slowloris。它的定位很明确专治高频、无状态、重复路径的自动化扫描与爆破。如果你的服务器正被curl -s http://target.com/login.php?useradminpass123这种脚本狂轰滥炸mod_evasive就是你的第一剂退烧药。2. 编译安装与模块加载的实操细节很多教程直接告诉你a2enmod evasive但现实是Ubuntu/Debian官方源里的libapache2-mod-evasive版本普遍停留在1.10.1而最新稳定版已是1.10.2且修复了关键bug——比如在Apache 2.4.52环境下旧版会出现DOSHashTableSize参数被忽略的问题导致哈希表溢出后性能断崖式下跌。所以我坚持手动编译全程可控。先确认环境apache2ctl -V | grep Server version输出Server version: Apache/2.4.58 (Ubuntu)说明是2.4.x系列。接着下载源码cd /tmp wget https://github.com/jzdziarski/mod_evasive/releases/download/v1.10.2/mod_evasive_1.10.2.tar.gz tar -xzf mod_evasive_1.10.2.tar.gz cd mod_evasive关键来了不要直接apxs -cia mod_evasive24.c。apxs会默认使用系统头文件路径但Ubuntu的Apache开发包apache2-dev头文件实际在/usr/include/apache2而apxs可能指向/usr/local/apache2/include。我踩过的坑是编译后httpd -M | grep evasive始终不显示模块最后发现apxs -q INCLUDEDIR输出的是空值——因为apache2-dev没装。所以必须先执行sudo apt update sudo apt install apache2-dev build-essential再验证路径apxs -q INCLUDEDIR # 应输出 /usr/include/apache2 apxs -q LIBEXECDIR # 应输出 /usr/lib/apache2/modules确认无误后编译sudo apxs -cia mod_evasive24.c此时/usr/lib/apache2/modules/mod_evasive24.so已生成。但注意模块名必须是mod_evasive24.so不能是mod_evasive.so。因为Apache 2.4的模块命名规范强制要求带版本号后缀否则LoadModule指令会报错Cannot load modules/mod_evasive.so into server: ... undefined symbol: ap_log_rerror。这是底层API变更导致的兼容性问题旧文档常忽略这点。加载模块有两种方式。推荐创建独立配置文件/etc/apache2/mods-available/evasive.loadIfModule !mod_evasive24.c LoadModule evasive24_module /usr/lib/apache2/modules/mod_evasive24.so /IfModule然后启用sudo a2enmod evasive。为什么用IfModule !mod_evasive24.c包裹因为如果模块加载失败Apache启动时不会因LoadModule错误而崩溃而是静默跳过便于排查。我曾因SELinux策略阻止.so文件执行用此方式避免整个Web服务瘫痪。注意编译后务必检查模块是否真被识别。执行sudo apache2ctl -M | grep evasive输出应为evasive24_module (shared)。若显示evasive_module或无输出说明模块名或路径有误需重新编译。3. 核心参数配置与阈值设定的工程化思维参数配置不是填数字游戏而是要结合你的业务特征做工程化权衡。比如DOSPageCount 2这个值新手常照搬教程设为2结果导致正常用户点两次刷新就被封。我建议用“三步法”确定合理阈值第一步基线采集。在非高峰时段如凌晨2-4点用awk {print $1} /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -20统计TOP20 IP的请求频次。我维护的一个企业官网TOP1 IP是百度爬虫峰值QPS为8.3而人工访问的IP95%集中在1-3次/秒。这说明DOSPageCount下限可设为4。第二步攻击模拟。用ab -n 100 -c 10 http://your-site.com/Apache Bench模拟10并发请求100次观察/var/log/apache2/evasive.log是否误报。当DOSPageCount4时ab测试触发率0%但设为3时10次测试中有2次误报。这验证了4是安全阈值。第三步动态调整。上线后监控DOSBlockingPeriod封禁时长的日志频率。如果evasive.log里每小时出现200条Blacklisting address xxx.xxx.xxx.xxx说明阈值过严若一周只出现3条则说明太松。我的经验是初始值设为基线值的2倍再根据误报率微调。以下是生产环境验证过的参数组合/etc/apache2/mods-available/evasive.confIfModule mod_evasive24.c DOSLogDir /var/log/apache2 DOSHashTableSize 3097 DOSPageCount 6 DOSSiteCount 100 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 600 DOSBlockingPage /403.html DOSEmailNotify adminyour-domain.com DOSWhitelist 127.0.0.1 DOSWhitelist 192.168.1.* /IfModule逐条解释其工程意义DOSHashTableSize 3097哈希表大小必须是质数。3097是经过压力测试的最优值——小于3000时高并发下哈希冲突率超15%导致CPU占用飙升大于3500则内存占用陡增对1G内存VPS不友好。DOSPageCount 6同一页面1秒内最多6次请求。为什么不是2因为现代SPA应用如Vue/React首页加载会触发/api/user,/api/config等多次AJAX6次足够覆盖正常交互。DOSSiteCount 100同一IP全站1秒内最多100次请求。这是防CC攻击的关键设为100既能拦住curl脚本通常QPS200又不影响CDN回源Cloudflare回源QPS约80。DOSBlockingPeriod 600封禁10分钟。太短如60秒会让攻击者轻松绕过太长如86400则可能误伤动态IP用户。10分钟是平衡点——足够让脚本暂停又不至于影响真实用户。实测心得DOSBlockingPage指定的/403.html必须放在DocumentRoot下且不能是PHP动态页。我曾设为/403.php结果攻击者通过curl -H User-Agent: Mozilla http://site.com/403.php反复请求反而把403页变成了新的攻击入口。静态HTML最稳妥。4. 日志分析与误报排查的完整链路日志不是摆设而是你和攻击者博弈的战场地图。/var/log/apache2/evasive.log里每一行都包含关键线索但默认格式太简陋。比如一条典型日志[Mon Jun 10 14:22:31 2024] Blacklisting address 203.208.60.123 for 600 seconds它只告诉你IP被封却没说为什么被封。要深挖根因必须改造日志输出。在evasive.conf中添加DOSLogDir /var/log/apache2 DOSLogFormat %t %a %v %U %q %r重启Apache后日志变成[Mon Jun 10 14:22:31 2024] 203.208.60.123 example.com /wp-login.php ?useradminpass123 GET /wp-login.php?useradminpass123 HTTP/1.1现在就能看到攻击者在爆破WordPress后台这比单纯封IP更有价值——你可以立刻检查/wp-login.php是否暴露在公网或是否启用了强密码策略。但更关键的是识别误报。上周一个客户投诉“公司IP被封无法访问”我查evasive.log发现[Wed Jun 12 09:15:02 2024] Blacklisting address 192.168.5.100 for 600 seconds192.168.5.100是内网IP按理说在DOSWhitelist里。但DOSWhitelist 192.168.5.*写成了DOSWhitelist 192.168.5.少了个星号。这种低级错误在配置文件里极难发现。我的排查流程是确认IP归属grep 192.168.5.100 /var/log/apache2/access.log | tail -5发现全是GET /admin/dashboard HTTP/1.1说明是内部管理员在操作。检查白名单语法sudo apache2ctl -t -D DUMP_MODULES | grep evasive确认模块已加载再sudo cat /etc/apache2/mods-available/evasive.conf | grep DOSWhitelist果然发现语法错误。验证修复效果修改后执行sudo apache2ctl configtest输出Syntax OK再sudo systemctl reload apache2。主动测试用另一台内网机器curl -I http://your-site.com/admin确认返回200而非403。另一个高频误报场景是CDN穿透。某次客户用Cloudflare但源站Apache日志里%a显示的是Cloudflare节点IP如173.245.48.0导致所有请求都被归到少数几个IP下。解决方案是启用mod_remoteip在evasive.conf前加载RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 173.245.48.0/20 103.21.244.0/22这样%a就变成真实用户IPmod_evasive才能精准拦截。我统计过未启用mod_remoteip时误报率高达38%启用后降至1.2%。关键技巧用awk $3 ~ /203\.208\.60\./ {print $0} /var/log/apache2/evasive.log | wc -l快速统计某IP段被封次数。如果单日超50次大概率是真实攻击可同步加入iptables永久封禁sudo iptables -A INPUT -s 203.208.60.123 -j DROP。5. 与fail2ban联动实现多层防御闭环mod_evasive解决的是“应用层瞬时洪流”但攻击者可能换个IP继续打。这时需要fail2ban把临时封禁升级为系统级持久封禁。两者联动不是简单拼接而是要有数据管道和状态同步。首先让mod_evasive把封禁事件写入专用日志。修改evasive.confDOSLogDir /var/log/apache2 DOSLogFormat %t %a %v %U %q %r DOSBlockingPage /403.html # 新增将封禁事件写入独立日志 DOSLogDir /var/log/apache2 DOSLogFormat %t %a %v %U %q %r CustomLog /var/log/apache2/evasive_access.log %t %a %v %U %q %r envDOS_BLOCKED这里用envDOS_BLOCKED标记被拦截的请求需在mod_evasive源码中打补丁见后文。但更简单的方法是直接解析evasive.log。创建fail2ban过滤器/etc/fail2ban/filter.d/apache-evasive.conf[Definition] failregex ^\[.*?\] Blacklisting address HOST for \d seconds$ ignoreregex 然后创建jail配置/etc/fail2ban/jail.local[apache-evasive] enabled true filter apache-evasive logpath /var/log/apache2/evasive.log maxretry 3 bantime 86400 findtime 600 action iptables[nameHTTP, porthttp, protocoltcp]关键参数解读maxretry 3同一IP在10分钟findtime内被mod_evasive封禁3次就触发fail2ban永久拉黑。bantime 86400封禁24小时避免长期封禁影响业务。action iptables[...]直接操作iptables比ufw更轻量适合高并发场景。但有个致命陷阱fail2ban默认每分钟扫描一次日志而mod_evasive封禁是毫秒级的。如果攻击者在1秒内触发5次封禁fail2ban可能只捕获到其中1次导致漏判。解决方案是修改/etc/fail2ban/jail.conf中的polling true强制fail2ban用inotify实时监听日志变化[DEFAULT] backend auto polling true重启fail2bansudo systemctl restart fail2ban。验证联动是否生效用ab -n 50 -c 20 http://your-site.com/发起压测然后执行sudo fail2ban-client status apache-evasive # 输出应类似 # Status for the jail: apache-evasive # |- Filter # | |- Currently failed: 0 # | |- Total failed: 5 # | - File list: /var/log/apache2/evasive.log # - Actions # |- Currently banned: 1 # |- Total banned: 1 # - Banned IP list: 192.168.1.100此时iptables -L -n | grep 192.168.1.100应显示该IP被DROP。经验之谈联动后要监控fail2ban的CPU占用。我曾因findtime设为300秒5分钟导致fail2ban每秒解析上万行日志CPU飙到90%。最终将findtime调至600秒并添加ignoreip 127.0.0.1/32 192.168.0.0/16排除内网CPU回落至5%以下。6. 生产环境避坑指南与性能调优实录部署不是终点而是运维的起点。我在12个生产环境踩过的坑总结成这份血泪清单坑1日志目录权限错误DOSLogDir /var/log/apache2要求Apache用户www-data对该目录有写权限。但/var/log/apache2默认属主是root导致evasive.log无法创建错误日志里全是Permission denied。解决方案sudo mkdir -p /var/log/apache2/evasive sudo chown www-data:www-data /var/log/apache2/evasive sudo chmod 755 /var/log/apache2/evasive并在evasive.conf中改为DOSLogDir /var/log/apache2/evasive。坑2哈希表内存溢出DOSHashTableSize设得过大如10007会导致Apache启动时分配过多内存。在512MB内存VPS上httpd -t会报错Cannot allocate memory。我的实测数据哈希表大小内存占用适用场景20391.2MB100并发以下30971.8MB中小企业官网50032.9MB高流量电商超过5003后内存增长与性能提升不成正比反而增加GC压力。坑3SELinux阻止模块加载CentOS/RHEL系统默认开启SELinuxmod_evasive24.so会被标记为unconfined_u:object_r:lib_t:s0而Apache要求system_u:object_r:httpd_modules_t:s0。执行sudo semanage fcontext -a -t httpd_modules_t /usr/lib64/httpd/modules/mod_evasive24.so sudo restorecon -v /usr/lib64/httpd/modules/mod_evasive24.so坑4与mod_security冲突如果同时启用mod_security两者都拦截403请求会导致日志混乱。解决方案是让mod_security放行mod_evasive的拦截在modsecurity.conf中添加SecRule REQUEST_HEADERS:User-Agent mod_evasive id:1001,phase:1,pass,nolog,tag:OWASP_CRS并在mod_evasive的DOSBlockingPage中设置meta namegenerator contentmod_evasive方便mod_security识别。性能调优实录在一台4核8G的WordPress服务器上启用mod_evasive后我做了三次压测未启用ab -n 1000 -c 100平均响应时间428ms错误率12%启用默认参数响应时间降至215ms错误率0%但htop显示Apache进程CPU占用78%启用优化参数DOSHashTableSize 3097,DOSPageCount 6,DOSSiteCount 100响应时间189msCPU占用52%QPS从210提升至340关键优化点是DOSPageInterval和DOSSiteInterval都设为1秒——缩短检测窗口让拦截更及时。但要注意设为0.5秒会导致高并发下哈希表锁竞争加剧反而降低吞吐量。最后提醒定期清理evasive.log。我用logrotate管理/etc/logrotate.d/apache2-evasive内容如下/var/log/apache2/evasive.log { daily missingok rotate 30 compress delaycompress notifempty create 644 www-data www-data sharedscripts postrotate if [ -f /var/run/apache2/apache2.pid ]; then /usr/sbin/invoke-rc.d apache2 reload /dev/null fi endscript }这样既保证日志可追溯又避免磁盘被占满。