Nginx封IP实战:从deny到OpenResty动态封禁

发布时间:2026/10/14 7:01:20

Nginx封IP实战:从deny到OpenResty动态封禁 Nginx 封 IP我这些年几乎每个项目都会碰到。不是服务器被爬虫刷到 CPU 报警就是登录接口被高频尝试弄到日志爆炸再不然就是某个接口被脚本反复调用直接把后端拖垮。拉黑 IP 听起来就是“拒绝访问”四个字真正落地的时候方案多、讲究也多。我见过有人直接在 nginx.conf 里堆几百条 deny 的也见过用 fail2ban 全自动封禁的还有用 OpenResty 做接口级动态拉黑的。今天这篇就把这几种常见做法从头到尾梳理一遍讲清楚设计思路、具体配置、验证方法以及我实际踩过的那些坑。这篇适合几类人服务器上跑着业务、但没有专职安全团队的开发者被爬虫和恶意请求反复折腾的运维同学以及刚接触 Nginx、想弄明白 access 模块和 geo 模块到底怎么用的人。看完之后你至少能自己搭一套“静态名单 动态封禁”的组合策略而不是一遇到攻击就无脑往配置文件里粘贴 deny。1. 先搞明白为什么要在 Nginx 这一层做拉黑1.1 哪些 IP 值得进黑名单很多人一上来就问“Nginx 怎么封 IP”但更关键的问题是你到底要封谁。我见过不少误伤案例把搜索引擎蜘蛛封了把自己公司出口 IP 封了甚至把正常用户的动态 IP 段给封了。所以动手之前先根据访问日志和业务特征把“恶意 IP”的分类搞清楚。常见的高危特征有这几类单 IP 在短时间内发起高频请求每秒几十次甚至上百次明显超出正常人操作频率。大量请求不存在的路径返回 404 的比例高得离谱多半是扫描器在探测后台地址。登录、注册、验证码这类接口连续失败比如同一 IP 十分钟内尝试登录几十次基本可以判定为撞库或密码爆破。User-Agent 异常清一色是 python-requests、curl、wget 之类的命令行工具或者 UA 字段干脆是空的。某个接口本身没做限流被脚本连续命中导致后端响应变慢。这些特征不一定单独出现往往是组合出现。判断标准不是“这个 IP 干了某件坏事”而是“这个 IP 的行为模式明显偏离正常用户”。日志里的真实数据是最重要的参考别凭感觉封。1.2 分层防护防火墙、Nginx、应用层各管一段“拉黑 IP”这个动作不是只能在 Nginx 里做。真正合理的架构是分层的。我是这么理解这三层的关系的流量最先经过防火墙然后是 Nginx最后才到应用代码。每一层都有各自的优势和代价。层次工作位置粒度性能维护成本典型场景防火墙iptables/firewalld/ipset网络层流量还没到 Nginx按 IP、端口、协议最高内核态直接处理名单太大时管理复杂大规模 IP 段封禁、全端口拒绝NginxHTTP 层请求已进入 Web 服务按 IP、URI、状态码、请求头会消耗少量 CPU但比应用层好得多配置文件维护简单按业务接口封禁、返回 403/444应用层业务代码内部可结合账号、行为、风控规则低攻击会打到业务代码灵活但最复杂账号维度限制、业务风险控制从纯性能角度说iptables 的 DROP 是最省资源的因为请求在网络层就被丢弃Nginx 根本不需要转发。但 iptables 只看 IP 和端口做不到“这个接口不许访问、那个接口允许访问”这种细粒度控制。Nginx 这边虽然要接收完整 HTTP 请求但它能把封禁逻辑做得更贴近业务。1.3 什么时候应该选 Nginx 而不是直接上防火墙我自己的选择逻辑是这样的如果攻击来源就是固定的几个 IP而且攻击目标不区分接口我会优先用防火墙一行命令加一条规则立即生效不用 reload Nginx。如果攻击是针对特定接口的比如只刷 /api/login 或者某个搜索接口那就在 Nginx 里按路径封禁因为只有 Nginx 能感知 URI。如果封禁名单非常大大到几千上万条也不建议堆在 Nginx 配置里这种规模适合上 ipset 这种专门干这个的内核工具。还有一个容易被忽略的点Nginx 的封禁可以让“恶意 IP 看得见被拒绝”比如返回 403方便排查而防火墙 DROP 的表现是连接超时客户端那边看着像是“根本没连上”。两种表现各有用途。如果你希望攻击者明确知道“我被你发现了”用 Nginx 返回 403如果你想消耗攻击者的连接资源让他的请求全部超时用防火墙 DROP。所以在做技术选型时先问自己三个问题要封的对象是 IP 还是 IP 加路径封禁名单会不会频繁变动是否需要在应用层做更复杂的风控判断答案清楚了选哪一层自然就明白了。2. 静态封禁deny/allow 的正确打开方式2.1 从一排 deny 说起基本配置和匹配顺序最朴素的做法就是在 server 块里直接写 deny。比如server { listen 80; server_name demo.example; deny 203.0.113.10; deny 198.51.100.0/24; allow all; }这段配置的意思是拒绝 IP 203.0.113.10拒绝 198.51.100.0 这个网段其他 IP 全部放行。很多人第一次写的时候容易把顺序弄反写成 allow all 在最前面。要注意 Nginx 的 access 模块是“按顺序匹配匹配到第一条就立即生效”所以只要 allow all 写在前面后面的 deny 全部失效。反过来如果我要做白名单模式也就是默认拒绝所有、只允许少数 IP 访问配置就变成location /admin/ { allow 192.0.2.10; allow 198.51.100.0/24; deny all; }这种模式通常用在后台管理页面、内部工具等敏感路径上。先写 allow 再写 deny all是因为匹配顺序在前面的规则先生效白名单 IP 会先命中 allow被放行其他 IP 则继续匹配到最后的 deny all被拒绝。这里有个非常隐蔽的坑deny/allow 可以写在 http、server、location 三个层级但内层如果重新定义了 access 规则外层的 access 配置就不会被继承。比如 server 层写了 deny 某个 IPlocation 里写了一个 allow all那么这个 location 会完全使用自己的规则外层 deny 对这个 location 里的请求不生效。这个行为不是我猜的Nginx 官方文档明确说了指令在内层重新出现时会覆盖整个继承链。排查“为什么封了没用”的时候要检查是不是有 location 覆盖了全局配置。2.2 geo 模块用一张名单管住成百上千个 IP如果 IP 数量到了几十上百条再在配置文件里一行行写 deny管理起来会很痛苦而且 access 模块是按顺序线性匹配的规则太多会白白消耗 CPU。这种规模我推荐用 geo 模块。geo 的作用是根据客户端 IP 生成一个自定义变量然后在 server 里用 if 判断这个变量。配置长这样geo $block_flag { default 0; 203.0.113.10 1; 198.51.100.0/24 1; 203.0.113.0/24 0; } server { listen 80; server_name demo.example; if ($block_flag 1) { return 403; } }这里的逻辑是geo 块定义在 http 层级Nginx 在解析请求时会根据客户端 IP 去匹配列表命中的 IP 或网段就把 $block_flag 设为对应的值没有命中就取 default 的值。当变量等于 1 时返回 403。geo 与 access 模块的 deny 有几个明显差异geo 支持同一个 IP 在多条规则里出现而且会按最长前缀匹配来选精确匹配优于网段网段越长优先。也就是说你可以在白名单里放一个精确 IP同时保持整个 /24 段被封处理起来比 deny 的严格顺序匹配灵活得多。另外 geo 内部用的是基数树做匹配即使列表很长查询效率也比线性扫描 deny 规则高不少。geo 跟上文说的 if 指令需要注意一点if 指令在 location 里有一些历史坑但只用来 return 一个状态码是安全的实际项目中大量使用这种组合放心用。2.3 静态配置的局限在哪里静态封禁有个天然问题名单需要人工维护。一个 IP 今天被写进配置明天攻击停了它可能还在名单里白白挡掉一个未来可能变成正常用户的地址。而且每次增删 IP 都要 reload Nginx虽然 reload 本身很轻量不改动 worker 进程的 PID但频繁操作总归是有成本的。我的经验是静态配置适合两类场景一类是高度确定的恶意来源比如已经确认发起过攻击的固定 IP这类 IP 可以长期拉黑。另一类是公司内部白名单长期不变用 allow 维护。真正需要频繁变化、需要根据行为自动响应的封禁需求老老实实做动态方案别指望人工维护静态配置能撑住。另外提一句性能边界deny 规则如果堆到上千条不推荐继续用 access 模块。我自己在一个项目里见过三千多条 deny 的配置文件虽然 Nginx 还能跑但每个请求都要线性过一遍规则明显浪费。这种规模要么用 geo 模块要么直接把名单交给防火墙的 ipset 去处理。3. 动态封禁把日志里的恶意 IP 自动揪出来3.1 先学会读 access.log动态封禁的第一步是能从访问日志里快速找出可疑 IP。Nginx 默认的 access.log 格式第一列就是客户端 IP所以最基本的统计命令就是用 awk 取第一列再排序计数awk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20这条命令的意思是把日志每一行的第一列也就是 IP打出来排序后统计每个 IP 出现的次数再按次数从大到小排序取前 20 个。输出结果里左边是请求次数右边是 IP。看到某个 IP 的请求次数是几万而其他 IP 只有几十不用多想这个 IP 基本就是问题来源。但只看总请求数还不够因为搜索引擎蜘蛛和监控探针也可能产生大量正常请求。更精准的做法是按状态码过滤。比如我想找“一直在探测不存在的路径”的 IP就统计返回 404 的请求awk $9 404 {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20这里的 $9 是 access.log 里的状态码字段具体第几列取决于你的 log_format 配置。如果你改了日志格式字段位置会变最好先 head 看一行日志确认列数。还有更组合的判断比如同时统计 403 和 404或者找出 User-Agent 里带 python-requests 的请求来源都可以用 awk 加条件实现。3.2 写脚本自动生成封禁名单查日志是人工排查真正生产环境要做的是自动化。最常见的方案是写一个 shell 脚本定期扫描日志把超过阈值次数的 IP 生成 deny 规则写进独立的配置文件然后 reload Nginx。我实际用过的脚本差不多是这样#!/bin/bash LOG/var/log/nginx/access.log TMP/tmp/nginx_block.$$.conf BLOCK/etc/nginx/conf.d/blocked_ips.conf WHITE/etc/nginx/conf.d/allow_ips.conf THRESHOLD100 # 统计 403/404 超过阈值的 IP生成 deny 行 awk $9 403 || $9 404 {print $1} $LOG \ | sort | uniq -c \ | awk -v t$THRESHOLD $1 t {print deny $2;} $TMP # 白名单必须放在 deny 前面保证先匹配放行规则 cat $WHITE $BLOCK cat $TMP $BLOCK rm -f $TMP # 配置校验通过再 reload避免语法错误导致服务中断 nginx -t nginx -s reload这里有两个细节值得展开说。第一为什么白名单要放在最前面因为 access 模块按顺序匹配白名单 IP 如果写在 deny 列表后面而它恰好又出现在黑名单里会被 deny 优先命中导致管理员自己都进不来。把白名单放前面就是让白名单规则先被匹配到相当于“特赦”。第二为什么要用临时文件再覆盖如果脚本执行到一半挂了或者磁盘满了直接覆盖正式配置文件可能会留下一份残缺文件。先写临时文件确认生成成功后再 mv 或 cat 覆盖可以把这种风险降到最低。不过这个脚本有个问题它每次全量重建 blocked_ips.conf会把之前封过、但本次日志统计里没再出现的 IP 全部清掉。实际运行中一个 IP 可能攻击完就安静了但你不一定希望它马上被解封。我一般会在脚本里加一个“历史名单合并”的步骤把上一次的名单保留下来或者规定一个封禁周期到期才允许自动解封。比如把脚本生成的新名单和历史名单做一个 sort -u 合并再清理超过 30 天的记录逻辑就完整多了。3.3 阈值怎么定才不容易误伤阈值是动态封禁里最敏感的参数。定太松恶意流量还没被拦住就已经把后端打挂了定太紧几分钟内多刷新几次页面的正常用户就被误封了。我习惯用日志数据说话而不是拍脑袋。先统计业务正常时期各个 IP 的每分钟请求数看 P90 和 P99 的值。比如某个资讯站点正常用户五分钟内不会打开超过 50 个页面接口调用频次是每分钟 20 次以内那么封禁阈值可以取正常值的 3 倍以上比如脚本里那个 100 次是针对某个后台场景调过的。不同接口、不同业务形态阈值完全不同千万不要一套数值到处套。封禁时长也一样。初次封禁我会给短一点比如 10 分钟让误伤的用户过一会儿能恢复访问。如果一个 IP 被反复触发说明它不是误伤而是有预谋的骚扰第二次可以封 1 小时再往后可以封 24 小时甚至更久。这种“阶梯式封禁”的思路比一次就封死要稳妥得多。另外要提醒一点判断恶意行为时尽量结合多个条件。比如一个 IP 返回 404 多不一定是坏事可能是搜索引擎在爬已经不存在的旧链接。但一个 IP 既返回大量 404又集中在几分钟内UA 还非主流那基本可以确定是扫描器。脚本里的条件越丰富误封率越低。3.4 想省事就用 fail2ban 思路如果你的技术栈里已经有 fail2ban那它其实是“日志分析 自动封禁 多动作联动”的现成方案不用自己写脚本调 nginx -t。核心原理很简单监控日志文件用正则表达式匹配失败痕迹超过 maxretry 次后就执行一个动作比如写 iptables 规则或者调某个命令加载 Nginx 封禁配置。一个针对 Nginx 的典型配置长这样放在 jail.local 里[nginx-block] enabled true filter nginx-block logpath /var/log/nginx/access.log maxretry 10 findtime 60 bantime 3600对应的 filter 配置文件里正则把状态码是 403 或 404 的请求行提取出来Fail2ban 会从这一行里解析出 IP 地址统计它在 60 秒内出现的次数超过 10 次就封禁 1 小时。Fail2ban 的优点是生态成熟支持很多现成的 filter 模板缺点是它依赖日志文件的轮转和监控响应有延迟而且日志从产生到被程序读到中间有时间差。对于真正的高并发攻击这几十秒延迟可能就是灾难所以我在解释完这套方案之后通常还会提醒一句如果攻击量大优先考虑下面这种在请求路径上实时拦截的方案。4. 接口级动态拉黑OpenResty Lua 方案4.1 为什么日志脚本还不够要上 OpenResty上面用脚本分析日志的方案本质上是一种“事后封禁”攻击已经发生了日志已经写了汇总脚本才把它揪出来。哪怕 cron 一分钟跑一次恶意 IP 在这一分钟之内也能发起上千次请求。想做到“刚超阈值就立刻拉黑下一个请求”就得把判断逻辑放在每次请求进入 Nginx 的那一刻这正是 OpenResty 的强项。OpenResty 是带有 Lua 脚本能力的 Nginx 发行版可以在请求处理的不同阶段执行 Lua 代码。我要在 access 阶段做两件事给当前 IP 的请求计数超过阈值就把这个 IP 写进共享内存里的封禁名单。因为计数和封禁都在内存里做不落盘响应是实时的比脚本方案快一个数量级。4.2 基于共享字典的计数和封禁实现先看一个简洁版的配置和 Lua 代码。这段逻辑可以直接放进 nginx.conf 里lua_shared_dict access_count 10m; lua_shared_dict blocked_ip 10m; server { listen 80; server_name demo.example; access_by_lua_block { local access_count ngx.shared.access_count local blocked_ip ngx.shared.blocked_ip local ip ngx.var.remote_addr local window 60 -- 统计窗口60 秒 local threshold 100 -- 窗口内超过 100 次即封禁 local block_expire 600 -- 封禁时长600 秒 -- 如果已经在封禁名单直接拒绝 if blocked_ip:get(ip) then return ngx.exit(403) end -- 计数加 1初始值为 0 local count, err access_count:incr(ip, 1, 0) if not count then ngx.log(ngx.ERR, count failed: , err) return end -- 第一次计数时给 key 设置过期时间实现 60 秒滚动窗口 if count 1 then access_count:expire(ip, window) end -- 超过阈值写入封禁名单并清理计数 if count threshold then blocked_ip:set(ip, true, block_expire) access_count:delete(ip) end } }逐段解释一下。lua_shared_dict 定义了两块共享内存一块用来计数一块用来存封禁名单。所有 worker 进程都能读写这两块字典修改是原子性的不会出现两个 worker 同时计数导致数据错乱的问题。access_by_lua_block 是 OpenResty 在请求处理到 access 阶段时插入执行的一段 Lua 代码。核心逻辑是先查封禁名单命中就返回 403不再往下走。然后给当前 IP 的计数器加一incr 的第三个参数是“初始值”意思是 key 不存在的时候按 0 开始加所以第一次请求后 count 为 1。如果 count 等于 1就给这个 key 设置 60 秒过期时间这样每个 IP 的自然计数窗口是 60 秒。当 count 超过 100 时把这个 IP 加进封禁名单600 秒之后自动解封。这里有一个实际经验的补充生产环境要注意共享内存的大小。10m 能存多少 key 取决于 key 长度和值类型通常能放几万个 IP 的计数器。如果业务量非常大可以观察 Nginx 错误日志里有没有 lua_shared_dict 内存不足的报错有的话要调大但也要警惕共享内存被恶意 IP 刷爆需要在上游再加一层 IP 白名单。4.3 Lua 封禁和 limit_req 的区别以及怎么配合很多人在做接口防护时会想到 Nginx 自带的 limit_req 模块这也是一种“请求太多就处理不过来”的防护方案但它的定位是限流不是拉黑。limit_req 用的是令牌桶算法超过速率限制后请求会排队或者返回 503它是“让你慢不让你打爆”但不会把具体的 IP 记入黑名单下一个请求来了还得再限一次。Lua 方案则是真正的“记名拉黑”超阈值就把 IP 写进黑名单在一定时间内直接拒绝不用每次重新判断速率。两种方案不冲突反而可以组合先用 limit_req 对不熟悉的请求做速率限制再用 Lua 对反复超限的 IP 做精准封禁。limit_req_zone $binary_remote_addr zoneapi_limit:10m rate10r/s; server { listen 80; server_name demo.example; location /api/ { limit_req zoneapi_limit burst20 nodelay; access_by_lua_block { -- 和上面一样的计数封禁逻辑 } } }这是我在实际项目里用得比较多的模式。接口正常情况下允许 10 QPS 每秒短时突发可以到 20 个请求如果有一个 IP 持续超速就会被 Lua 封禁 10 分钟。这样既不伤正常用户又能把高频脚本逼走。有读者可能会问OpenResty 的 Lua 逻辑写在 nginx.conf 里维护起来方便吗我的建议是逻辑简单就写在 access_by_lua_block 里逻辑复杂就单独建 Lua 文件用 lua_package_path 引用。另外如果公司规模一把梭上了 K8s每台实例自己维护一份封禁名单封禁效果其实是碎片化的更理想的方案是把计数和封禁状态放到 Redis 里做成全局共享。不过那是另一个量级的复杂度单机或小集群场景先把手上的方案跑通再说。5. 验证、排错与几个容易翻车的地方5.1 封禁生效了没有这样验证最直接配置写完之后第一件事是语法检查然后 reloadnginx -t nginx -s reloadnginx -t 会检查所有配置文件的语法如果有报错它会明确告诉你是哪个文件第几行出的问题。reload 是平滑重载已经在处理的请求不会中断新的请求会走新配置这个特性让封禁上线能够无缝进行。验证是否真的封住了最常规的方法是在一个“被封 IP”的环境里去访问目标站点看返回状态码curl -s -o /dev/null -w %{http_code}\n http://demo.example/预期输出 403。如果配置里用的是 return 444curl 可能会提示“Connection closed without response”这也算正常444 的意思是直接断开连接不返回任何响应体和状态码这是 Nginx 特有的非标准状态码对付扫描器很有用。如果没有第二台机器可以模拟“被封 IP”还有一个笨办法但很有效临时把自己当前出口 IP 加进 deny 列表然后自己访问自己测完再删掉。不过要小心万一配置疏忽可能会把自己一直封在外面。我一般是在服务器上先测试确认没问题后再让前端同事从办公网络验证。5.2 配置了 deny 却没有效果怎么排查“明明封了对方还能访问”是我被问得最多的问题。这类问题绝大多数不是 Nginx 不听话而是封的位置或者判断条件有偏差。我把常见原因整理成了下面这张速查表现象可能原因解决办法配置文件改了但请求还是能进来配置没有 reload或者 reload 前就被 nginx -t 发现语法错误确认 nginx -t 通过再执行 nginx -s reloaddeny 写在 server 层某个 location 没有效果这个 location 里重新定义了 access 规则覆盖了外层在 location 里也补上同样的 deny或把规则统一放到 http 层封的是 IPv4客户端通过 IPv6 访问IPv4/IPv6 被当成不同来源在 deny 里同时处理 IPv6 网段比如 deny ::1;前面有 CDN 或代理封不到真实 IP$remote_addr 一直是 CDN 节点 IP配置 set_real_ip_from 和 real_ip_header取到真实客户端 IP封的是 A 域名攻击走 B 域名server 块配置没有继承到目标虚拟主机确认被封的域名实际命中哪一段 server 配置浏览器里还是能打开可能命中缓存或者源站返回内容被中间层缓存直接用 curl 测试源站状态码判断这里有个值得展开的细节就是 location 覆盖的问题。Nginx 的指令继承规则是内层没有定义相关指令时会继承外层的但内层一旦定义了外层的同类配置就被整体覆盖不是合并。所以如果你在 http 块里 deny 了一个 IP又在某个 location 里写了 allow all这个 location 就会完全忽略 http 层的 deny 列表。这不是 bug是设计但很多人都踩过。还有一种很隐蔽的情况攻击是通过 HTTPS 的 443 端口进来的但你封禁只写在监听 80 的 server 块另一个监听 443 的 server 块完全没有配置。建议把封禁规则放在 http 块或公共的 include 文件里保证所有 server 都生效。5.3 有 CDN 或反向代理时别封错了对象如果站点前面挂了 CDN或者 Nginx 前面还有一层负载均衡那么 $remote_addr 拿到的是 CDN 节点或负载均衡的 IP而不是真实用户 IP。在这种架构下封禁如果不处理真实 IP要么封了个寂寞封的永远是代理节点要么一怒之下把 CDN 整个 IP 段封了导致所有真实用户都无法访问。解决办法是使用 Nginx 的 realip 模块让它从可信代理传递的 X-Forwarded-For 头里取出真实客户端 IP。配置如下set_real_ip_from 203.0.113.0/24; real_ip_header X-Forwarded-For; real_ip_recursive on;要理解这三行的关系。set_real_ip_from 声明哪些 IP 段是你的可信代理这些 IP 发请求时可以信任它们携带的 X-Forwarded-For 头。real_ip_header 指明真实 IP 在哪个头里。real_ip_recursive 的作用是解决多级代理的情况X-Forwarded-For 右边是最近的代理 IP往左才是真实的客户端链开启 recursive 后Nginx 会从右往左跳过所有可信代理取最左边那个真实客户端 IP。这里必须强调绝对不要在没配 set_real_ip_from 的情况下去信任 X-Forwarded-For。因为客户端可以自己随便造这个头直接伪造一个别的 IP到时候你封禁的列表就形同虚设攻击者换个 X-Forwarded-For 就能绕过。很多“封了还进来”的问题根源其实在这里。5.4 自己把自己封了以及误封蜘蛛的问题动态封禁跑起来之后最尴尬的一件事是把自己公司的出口 IP 给封了。它有可能会在某个时间段集中访问被脚本误判成高频攻击。所以做动态封禁之前第一件事就是把公司出口 IP、办公室网段、运维跳板机 IP 加入白名单并且保证白名单的匹配优先级在 deny 之前。搜索引擎蜘蛛也容易踩雷。Googlebot、Baiduspider 等蜘蛛会从一个 IP 段集中发请求数量不少如果阈值设得低很容易被当成爬虫封掉。误封蜘蛛的直接后果是网站在搜索引擎里的收录和排名下降。我的建议是在统计脚本或 Lua 逻辑里对 UA 里有明显蜘蛛标识的请求单独放行或者在 Nginx 里对这些 UA 直接返回正常响应不进计数if ($http_user_agent ~* Googlebot|Baiduspider|Bingbot) { set $spider_flag 1; }不过也要承认现在很多恶意爬虫会伪造蜘蛛 UA所以只凭 UA 判断并不完全可靠。更稳妥的方案是在动态封禁的列表里做一个延迟生效机制某个 IP 第一次触发封禁时先返回一个验证码页面或者让请求变慢第二次触发才真的封死。这种“先给机会不改就封”的思路能大幅降低误封率。最后分享一个我现在比较习惯的组合策略。入口流量先用防火墙做粗粒度拦截明显异常的 IP 段、超大批量的扫描直接 DROP不浪费 Web 层资源。业务侧用 Nginx 维护一个规模可控的动态封禁名单变化不频繁reload 成本低按路径封接口也顺手。真正高频且关键的业务接口比如登录和下单则用 OpenResty 在请求阶段做计数拉黑实时性最好。这套组合在多个项目里跑下来误封率低恶意流量基本在第一层就被消掉大半。工具链不复杂关键是别指望一个方案解决所有问题每层做每层该做的事边界划清楚Nginx 拉黑 IP 这件事才真正算做到位了。
延伸阅读

更多相关文章

2026/10/14 7:01:20

RAG实战:从文档索引到混合检索与生成约束的完整链路

第一次正经做 RAG(Retrieval-Augmented Generation,检索增强生成)相关的项目,是被一个内部知识库问答需求逼上山的。某公司想把几百份产品文档、售后工单和技术规范整理成一个问答机器人,把客服那边重复到快麻木的&quo…

2026/10/14 6:56:20

智能面积:AI Agent从精度指标到能力边界的度量革命

1. “智能面积”不是新概念,而是AI Agent落地的度量革命最近在某跨平台系统项目里做Agent架构评审时,团队反复卡在一个问题上:我们花三个月搭出来的客服Agent,到底算“做得好”还是“凑合能用”?当时大家列了一堆指标—…

2026/10/14 6:56:20

Agent框架选型实战指南:从状态流建模到生产落地

1. 这不是框架罗列,而是选型决策现场实录“Agent基础(5):常见框架与选型”——看到这个标题,你大概率正站在一个真实而紧迫的岔路口:手头有个需要自主决策、多步调用、带记忆和工具能力的智能体任务&#x…

2026/10/14 15:35:21

用Flask与ChatGPT打造微信公众号AI自动回复机器人

简介:一份基于Flask与ChatGPT的微信公众号聊天机器人后端工程,面向已有Python基础、希望快速搭建公众号智能对话服务的开发者,也适合用于学习Flask接口开发、ChatGPT API接入和Docker容器化部署。压缩包内含可运行的完整源码和部署文档&#…

2026/10/14 15:35:21

汽车行业亟须健全第三方评测机制

汽车工业越向高端化、智能化演进,安全评测机制的公共属性就愈发重要。标准更高更严,评测更真实更可信,监管部门、企业、媒体及第三方机构各尽其责,中国汽车行业发展才会更规范、更健康。新华财经上海10月12日电(记者 李…

2026/10/14 15:35:21

3D三角形网格补洞源代码解析:从边界环检测到水密模型修复

简介:一套面向3D图形开发者和算法学习者的三角形网格模型补洞源码工程,解决模型导入导出时出现的孔洞、拓扑不连续等问题。包内含完整C11工程与第三方Eigen数学库,覆盖边环检测、新面生成、拓扑更新、平滑处理等补洞步骤;工程基于…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑