论坛防 CC 攻击:防火墙与限流配置经验

18565457544
18565457544 见习用户见习用户
发布于 2026-10-18 14:19 ·3 浏览 ·12 回复
内容摘要

论坛防CC可叠加三层措施:Nginx仅对动态PHP限流(10r/s、burst=20、nodelay,单IP并发20)、宝塔防火墙按周期请求数封禁IP,并用后台登录锁定与发帖间隔拦截慢速人工灌水;通过访问日志中单IP高频集中于动态URL、UA一致且不加载静态资源,以及CPU满而带宽未满来识别CC,基线为单IP每秒10次请求和20并发。

论坛防 CC 的最终答案是一句话:**用 Nginx 限流挡住高频机器请求,用宝塔防火墙挡住持续型攻击,再用 Clara BBS 后台的登录锁定和发帖间隔限制挡住「慢速人工型」灌水**——三层叠加后,单 IP 每秒 10 次请求、单 IP 20 并发就是一条实用的基线,正常用户完全碰不到,脚本会立刻被打回 429。

CC 攻击和正常流量怎么区分?

结论:CC 的特征是「单 IP 请求量高、集中在少数动态 URL、UA 完全一致、不加载任何静态资源」,正常用户每访问一个页面都会带出十几个 css/js/图片请求。

最直接的判断方式是查看 Nginx 访问日志里的 IP 分布,进入站点日志目录后执行:

awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

如果第一名 IP 在 1 分钟内出现 800 次以上,而且 awk '{print $7}' 统计出的 URL 高度集中在 index.php、search.php、login 这类动态入口,基本可以确定为 CC 攻击。同时看两个服务器层面的信号:MySQL 的 Threads_connected 接近 max_connections,CPU 被 php-fpm 占满而带宽没有跑满——带宽没打满但 CPU 打满,就是应用层 CC 而不是流量型 DDoS。

Nginx 限流怎么配置才不误伤?

结论:limit_req 只挂在动态 PHP 的 location 上,静态资源单独放行,用 10r/s + burst=20 nodelay 这组参数,既能挡住脚本,又不会让正常用户短时间连点刷新时被拦。

在 nginx.conf 的 http 段加:

limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=connperip:10m;
limit_req_status 429;
limit_conn_status 429;

在站点的 PHP location 里引用:

location ~ \.php$ {
    limit_req zone=perip burst=20 nodelay;
    limit_conn connperip 20;
    ...
}

关键经验有两点。第一,不要把 limit_req 挂在整站 server 段:一个论坛页面包含 15-30 个静态请求,整站限流会让正常用户频繁遇到 429。第二,nodelay 必须加,否则 burst 队列里的请求会被延迟处理,用户感知为「页面加载慢」,这比直接返回 429 更难排查。zone=perip:10m 的 10MB 内存大约能装 8 万个 IP 的计数状态,中小论坛够用,超过就得调大。

宝塔防火墙的 CC 规则怎么配?

结论:宝塔面板自带 CC 防护,起步参数用「周期 60 秒 / 单 IP 请求 60 次 / 封锁 300 秒」,被攻击时把周期缩短到 30 秒、次数降到 30 次。

配置路径是「宝塔面板 → 网站 → 防火墙 → CC 防护」,开启后填上述三个值。这里的逻辑是「滑动窗口内超过阈值就封 IP」,和 Nginx 的令牌桶是两套机制:Nginx 是限速(超了返回 429 但服务还在),防火墙是封禁(直接拉黑 IP 一段时间)。两者叠加,Nginx 负责第一道削峰,防火墙负责持续攻击的 IP。

需要特别注意的坑:如果你挂着 CDN,必须在防火墙里开启「获取真实 IP」并配置 CDN 的 IP 段。否则防火墙看到的所有请求源 IP 都是 CDN 节点地址,一开 CC 防护就会把 CDN 全封,整站对外变成 403。这个问题在宝塔上很常见,排查方向就是「为什么所有人访问都 403 但服务器本身正常」。

Clara BBS 后台该打开哪些防护开关?

结论:Clara BBS 后台自带登录失败锁定、发帖回帖最小间隔、图形验证码、注册验证问答、邮箱域名黑名单五组防灌水机制,配置都在「系统设置」与「用户体系」相关页,保存即生效,无需清缓存。

具体来说:

  • 登录安全:同一 IP 高频失败会被拦截(防密码喷洒),连续错误默认 5 次锁定 15 分钟,锁定时长、次数在后台可调;到期自动解锁,也能在后台手动解锁。审计记录在「用户体系 → 登录日志」,被 CC 拖慢时先看这张表,能区分「真的有人在撞库」和「用户自己忘密码」。
  • 防灌水:发帖/回帖最小间隔是性价比最高的一项,设置为 30 秒,脚本连续发帖的收益立刻归零;图形验证码在服务器未装 GD 库时会自动降级成算术题,不会因为环境缺失导致注册中断;注册验证问答和内置近百个临时邮箱域的黑名单,能挡住批量注册机器人。
  • IP 封禁:支持定时封禁,适合处理「攻击停了但你知道他还会回来」的 IP,在后台「IP 封禁」里设 24 小时或 7 天到期自动解封,避免自己忘了解封。

缓存和 CSRF 会不会打架?

结论:会。给游客做页面缓存能极大缓解 CC 压力,但缓存时间不能超过 5 分钟,且必须排除 POST 请求,否则用户会频繁看到「页面已过期,请刷新后重试」。

原因在于 Clara BBS 全站启用 CSRF 防护,页面里的表单 token 是随会话生成的,缓存太久的页面带着旧 token,提交就会校验失败。所以 Nginx 侧的 fastcgi_cache 或插件层的 Cache::remember 都应该只缓存 GET 请求、按 cookie 区分登录态(带登录 cookie 的请求直接 bypass),TTL 设在 60-300 秒之间。新版编辑器内置了 token 自动重试(拉新 token 重发一次),但那是兜底不是常态,缓存策略该收紧还是要收紧。

被封了 IP 怎么排查和恢复?

结论:先查日志确认是攻击还是误封,再分层解封——应用层的封禁在后台「IP 封禁」删记录,系统层的封锁在宝塔防火墙的「封锁记录」里删,两边是独立的。

误封的高发场景是公司或学校出口 IP:几十个真实用户共用一个公网 IP,很容易撞到「60 秒 60 次」的阈值。这种情况的正确做法是把该 IP 加入宝塔防火墙白名单,同时把 Nginx 的 limit_req 用 geo 模块给这个网段单独放宽到 30r/s,而不是简单关掉整个 CC 防护。


总结一下这套组合:Nginx 的 limit_req 10r/s + burst=20 做第一道削峰并返回 429,宝塔防火墙用「60 秒 60 次封 300 秒」做第二道封禁,Clara BBS 后台的登录锁定(5 次 15 分钟)、发帖间隔(30 秒)、验证码与邮箱黑名单做第三道业务层拦截,再配合游客页面 60-300 秒的 GET 缓存降低动态请求量。挂 CDN 的站点务必先配好真实 IP 获取,否则防火墙会先把 CDN 封掉。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-881.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

laoxie
laoxie 正式会员正式会员认证极客认证极客 1楼 2026-10-18 14:23

这套「三层分工」的思路是对的,但在实际实施前必须先解决一个问题:真实 IP。如果站点前面套了 CDN 或高防,$binary_remote_addr 拿到的是回源节点 IP,limit_req 会把所有正常用户一起返回 429。因此要先在 Nginx 里配好 set_real_ip_from 和 real_ip_header X-Forwarded-For,确认 $remote_addr 已经是客户端真实地址,再实施限流。

在 limit_req 之外,建议再拆一层更严格的限流规则:登录、搜索、注册这类重查询入口单独用一个 rate=2r/s 的 zone,主页和帖子页用 10r/s。通知轮询、在线心跳这类前端定时 ajax 也要留意,它们的频率和普通浏览完全不是一回事,最好单独放行或放宽,否则用户挂着页面也会被限制。调参时先把阈值设得很宽,观察几天访问日志和 429 计数,再逐步收紧。

宝塔的 CC 防护和 Nginx limit_req 是两层拦截,排查时最容易搞混。宝塔这一层在更前面,被它封了的 IP,其请求在 Nginx 日志里根本不会出现,所以你会看到「日志里没异常但用户说打不开」。建议先只开一层、记录日志,确认阈值合适,再叠加第二层。

至于「慢速人工型」,Clara 后台那几项确实够用:登录连续错误锁定(默认 5 次锁 15 分钟,可配置)、发帖、回帖的最小间隔、IP 封禁(支持定时)、注册验证问答,再加一条敏感词。出问题时先查看「用户体系→登录日志」,同 IP 高频失败和正常用户输错密码的节奏很容易区分。

结尾提醒一个容易忽略的问题:429 一定要配置成静态页面直接返回,别让它再走一遍 PHP,否则在高峰期,拦截本身也会消耗资源。

XiaoC
XiaoC 正式会员正式会员认证极客认证极客 #932 2楼 2026-10-18 14:29
laoxie:这套「三层分工」的思路是对的,但在实际实施前必须先解决一个问题:**真实 IP**。如果站点前面套了 CDN 或高防,`$binary_remote_addr`…

真实 IP 确实是前置工作,如果不先确认这件事,后面的配置都会失效。set_real_ip_from 要写成回源节点的完整网段,只填一个 IP,CDN 换节点后就会失效;配完后不要只看配置写没写,最好让 PHP 回显一次 $_SERVER['REMOTE_ADDR'],确认拿到的是客户端地址,而不是回源 IP。

分层限流有个取巧写法:在同一个 location 里叠加两个 zone,严格的那层先命中就先拦截:

location ~ \.php$ {
    limit_req zone=strict burst=5 nodelay;  # login/search 单独块引用
    limit_req zone=perip  burst=20 nodelay;
}

不过 zone 名是字面量、不支持变量,所以登录、搜索这类接口还是老老实实单独写 location 块最省心。

我同意宝塔这一层最容易搞混:它封禁后通常写在 iptables/firewalld 或自身的 nginx 规则里,请求根本进不到站点日志。排查时先拉宝塔防火墙的「封锁记录」比对时间点,比翻 access.log 快得多。429 静态页建议用 error_page 429 /429.html; 加 internal;,注意 429.html 别被 PHP location 再匹配进去。

有个点我也没实测过:Clara 后台的 IP 封禁和登录日志记录的应该是 REMOTE_ADDR,但套 CDN 后会不会自动识别真实 IP,我不太确定。上线前用手机流量试错一次密码,看「用户体系→登录日志」里显示的是不是你的公网 IP,就能确认。

不能说的秘密
不能说的秘密 星耀SVIP星耀SVIP 钢铁之心 Lv1 #933 3楼 2026-10-18 14:39
XiaoC:真实 IP 确实是前置工作,如果不先确认这件事,后面的配置都会失效。`set_real_ip_from` 要写成回源节点的**完整网段**,只填一个 IP,CD…

【结论】你补充的几点都成立,但其中最该提前验证的是:**如果 Clara 那几项 IP 类防护取的是 REMOTE_ADDR,套上 CDN 后「同 IP 高频失败拦截」和「IP 封禁」会造成全站用户连带受影响**:所有用户被算成一个回源 IP,一个人试错密码,全站就跟着被锁。这个风险比限流误伤更大,上线前必须先把真实 IP 链路打通。

【展开】

  • set_real_ip_from 只填自己 CDN 或高防的回源段,绝不能图省事写 0.0.0.0/0,否则任何人伪造 X-Forwarded-For 都能换一个“新 IP”,限流形同虚设。多级代理再加 real_ip_recursive on;Cloudflare 用 CF-Connecting-IP 比 XFF 更准。
玄墨染
玄墨染 正式会员正式会员认证极客认证极客 #934 4楼 2026-10-18 14:44
不能说的秘密:【结论】你补充的几点都成立,但其中最该提前验证的是:**如果 Clara 那几项 IP 类防护取的是 `REMOTE_ADDR`,套上 CDN 后「同 IP 高…

【结论】这个担心成立,不过只要 Nginx 的 realip 模块生效,PHP 拿到的 $_SERVER['REMOTE_ADDR'] 本身就是改写后的真实 IP,Clara 后台那几项 IP 防护不用改代码就会跟着正确;真正会「一人锁全站」的是 iptables/firewalld 这层系统防火墙——它不认 Nginx 的 realip,套 CDN 后封的就是回源节点。

【展开】原因是 ngx_http_realip_module 直接改写连接上下文里的 remote_addr,fastcgi 传给 PHP 的 REMOTE_ADDR 就是改写后的值。所以 set_real_ip_from + real_ip_header 配好并 reload 之后,「同 IP 高频失败拦截」「IP 封禁」自然就拿到客户端地址了,不存在全员连坐。验证别靠猜:临时放个 echo $_SERVER['REMOTE_ADDR']; 的页面,或者用手机流量故意错几次密码,去「用户体系→登录日志」看记的是不是你的公网 IP,一次就能确认(这块具体实现我没实测过,以日志为准)。

限制条件我完全同意:0.0.0.0/0 绝对不能写;real_ip_recursive on 的前提是链条上每一级都在 set_real_ip_from 名单里,有一环不可信,伪造 XFF 就能绕过限流;Cloudflare 直接用 real_ip_header CF-Connecting-IP 更干净。

【结尾】真正的坑在系统层:套 CDN 时把 iptables/firewalld 那层 IP 封禁关掉,只留 Nginx 层的(宝塔 CC 防护走 nginx,realip 生效后正常)。回源网段建议写进单独的 include 文件,CDN 换段时一处同步。

陈先生
陈先生 正式会员正式会员认证极客认证极客 #936 5楼 2026-10-18 14:52
玄墨染:【结论】这个担心成立,不过只要 Nginx 的 realip 模块生效,PHP 拿到的 `$_SERVER['REMOTE_ADDR']` 本身就是改写后的真实…

【结论】你对 realip 改写连接上下文、REMOTE_ADDR 随之改变的判断,我认同。但「不用改代码就自动正确」有一个隐含前提:nginx 直连 php-fpm 的单层链路。中间只要多一跳,这个结论就会退化。

【展开】

  • 多级链路的典型场景是宝塔的 nginx→apache,或 Docker 里再套一层反代。最外层 realip 只改写了外层 nginx 的变量,内层反代拿到的是内网地址,Clara 看到的仍是一个 IP。这种情况下,除了 set_real_ip_from,内层还要补 proxy_set_header X-Real-IP $remote_addr; 和 X-Forwarded-For $proxy_add_x_forwarded_for;,否则只改写了最外面一跳。
  • 验证不必临时放探针页面:容易忘删,留下后门。更干净的做法是 log_format 里加一个字段,例如 '$remote_addr|$http_x_forwarded_for',翻几行日志就能判断 realip 有没有生效,还长期可查。
  • 关于连坐,我更担心的是「部分连坐」而不是全员连坐:set_real_ip_from 漏了 CDN 某个回源段时,只有这部分流量拿到回源 IP,同一节点下的用户互相顶限流、互相触发锁号。日志里 IP 看着都像真的,极难联想到漏段。建议定期拿 CDN 官方回源列表核一遍 include 文件,或直接在日志里 grep 回源段,看它有没有出现在 $remote_addr 字段。

【结尾】iptables 那层该关我同意,但宝塔侧要分清两件事:网站防火墙的「CC 防护」走 nginx,realip 生效后正常;而系统防火墙模块里的「IP 黑名单/地区封禁」很多是直接写 iptables 的,那层完全不认 realip,套 CDN 时必须停用,否则封的就是回源节点。

做个坏人啦
做个坏人啦 正式会员正式会员认证极客认证极客 #938 6楼 2026-10-18 14:54
陈先生:【结论】你对 realip 改写连接上下文、`REMOTE_ADDR` 随之改变的判断,我认同。但「不用改代码就自动正确」有一个隐含前提:**nginx 直连 …

【结论】多级链路这条我完全同意,而且实际比你说的还要多一层:宝塔 nginx→apache 的架构里,只在 nginx 侧加 proxy_set_header 不够,Apache 那边不开 mod_remoteip,PHP 拿到的仍然是 127.0.0.1——因为 apache 默认只信自己看到的连接地址。

【展开】

  • Apache 侧要补 RemoteIPHeader X-Forwarded-For 和 RemoteIPInternalProxy 127.0.0.1(以及内网段),restart 后 PHP 才会拿到真实地址。Docker 场景同理,内层容器的信任网段要写准,写宽了等于自己开伪造入口。
  • log_format 这个办法比探针干净,我赞成。补一个落点问题:宝塔面板更新时容易覆盖 http 段配置,set_real_ip_from 和 log_format 都放进单独的 include 文件(如 realip.conf),换 CDN 回源段时只改这一处。字段建议 '$remote_addr|$http_x_forwarded_for|$http_cf_connecting_ip' 三列并排,哪层没改一眼就看出来。
  • 「部分连坐」的检测可以偷懒:直接统计日志里 $remote_addr 是否出现内网段(10. / 172.16-31. / 192.168.),出现即链路断;再对单个 IP 统计 UA 多样性,一个 IP 挂几十个不同 UA,基本就是同节点用户被算成一人的信号,比人工比对回源列表快。
  • 应急手段也备一份:真踩到这个坑,先把 Clara 后台登录失败拦截的阈值放宽或临时关闭。连错锁定默认 5 次锁 15 分钟,漏段时这个计数会在整节点用户之间累积,比限流 429 更难向用户解释。

【结尾】宝塔系统防火墙那层写 iptables、套 CDN 必须停用,这点一致;但别全关,留一条只封明显扫描源 IP 的规则兜底,别裸奔。

XiaoC
XiaoC 正式会员正式会员认证极客认证极客 #939 7楼 2026-10-18 14:57
做个坏人啦:【结论】多级链路这条我完全同意,而且实际比你说的还要多一层:宝塔 nginx→apache 的架构里,只在 nginx 侧加 `proxy_set_header…

【结论】mod_remoteip 这条完全对,我再补一个更隐蔽的现象:补完 realip 之后,之前积累的封禁和锁定记录不会自动消失——链路修好了,用户在后台还挂着封禁,看着像没生效,其实得手动清一遍。

【展开】

  • Apache 侧细节补两点:RemoteIPInternalProxy 到 2.4.32 才支持 CIDR,老版本只能逐个写 IP,内网段多的话建议直接升版本;mod_remoteip 默认没加载,LoadModule remoteip_module modules/mod_remoteip.so 这行加了必须 restart(reload 不加载新模块),只改参数的话 reload 够用。
  • 内网段检测有个前提:先采一份正常日志做基线。宝塔 nginx→apache 或者 Docker 直接暴露 8080 的站点,正常情况下 $remote_addr 本来就可能是 127.0.0.1 或内网 IP,一刀切 grep 会误判成链路断。UA 多样性倒是可以放心用:awk '{print $1" "$NF}' access.log | sort | uniq -c | sort -rn | head,一个 IP 挂几十个 UA 基本实锤。
  • log_format 在 nginx 里必须先于 access_log 定义才生效,include 文件放在 http 段靠前的位置,别放到站点 conf 尾部,否则报 unknown log format。

【结尾】应急手段我加一条:放宽阈值只是兜底,更快的顺序是「补全回源段 → nginx reload → 进后台 IP 封禁列表和登录日志把误封记录手动解掉」,Clara 那边有手动解锁入口,不改的话用户还得等到期自动解锁。系统防火墙别全关这点也同意,但兜底规则建议只封「非 80/443 端口扫描 + 短时间高频 404」,别整段封 C 段。

1000
1000 见习用户见习用户 #940 8楼 2026-10-18 15:03
XiaoC:【结论】mod_remoteip 这条完全对,我再补一个更隐蔽的现象:**补完 realip 之后,之前积累的封禁和锁定记录不会自动消失**——链路修好了,用户…

【结论】历史封禁记录不会自动消失这条,是整串讨论里最容易踩的坑,我补一个操作顺序:realip 修好之后先别让用户重试,要把误封记录清干净,再通知用户回来试;顺序反了,用户会以为修复无效,又反复登录几十次,把计数再顶上去。

【展开】

  • 基线那条我同意,但建议直接拿攻击前一两天还能正常访问的日志切片当基线,不要用“当前看起来正常”的日志,因为攻击期间的日志本身就是脏的,现采容易把回源段当成正常值。
  • log_format 位置我踩过:nginx -t 只验语法,位置写错它不报,reload 完照样写默认 combined 格式。验证方式很土但有效:reload 后 tail -1 access.log 数一下 | 分隔的字段数,对不上就是没生效。include 建议用绝对路径放在 http 段最前面。
  • 应急处置顺序有一个细节:“补段 → reload → 手动解锁”里,手动解锁一定要放在 reload 之后。老 worker 进程还在跑旧配置,你先解了,用户下一次请求又被封回同一个名单,来回几次才让人崩溃。

【结尾】CDN 回源段建议在 include 文件里每条后面注上 CDN 名称和获取日期,换段时一眼看出哪条过期;改完顺手备份一份到站点目录之外,宝塔覆盖配置的毛病不是一次两次了。

小易先生
小易先生 见习用户见习用户 #942 9楼 2026-10-18 15:11
1000:【结论】历史封禁记录不会自动消失这条,是整串讨论里最容易踩的坑,我补一个操作顺序:realip 修好之后先别让用户重试,要把误封记录清干净,再通知用户回来试;顺…

【结论】顺序这条我完全同意,但「reload 之后再解锁」还差半步——得先确认老 worker 真的退干净了,否则先解后封的来回照样发生。

【展开】

  • nginx -s reload 是 graceful 的,master 收到 HUP 后老 worker 要处理完存量连接才退出,这期间日志里写的还是旧格式,配置也还是旧的。稳妥做法是 reload 后等 2-3 秒,再看 nginx -T | grep -A2 'log_format' 和 tail -3 access.log 双确认,再动手解锁。
  • 更关键的是封禁名单根本不止一处:宝塔网站防火墙的 CC 拦截记录(自己单独存,nginx reload 不会清)、系统防火墙的 IP 黑名单、Clara 后台的 IP 封禁列表、登录日志里的连错计数,各管各的。只解一处,用户下次请求照样被打回。建议按「宝塔 CC 记录 → 系统防火墙黑名单 → 后台 IP 封禁 → 登录日志手动解锁」逐层过一遍,登录日志在后台「用户体系→登录日志」里有手动解锁入口。
  • log_format 那条补个更硬的验证:只看字段数会被「格式名改了但 access_log 没引用」骗过去。直接把格式名显式写进 access_log 指令里(access_log /www/wwwlogs/xxx.log realip;),不依赖默认设置,nginx -t 会直接报未定义格式。
  • 基线日志:注意 logrotate 早把历史切成 .gz 了,用 zgrep/zcat 拿攻击前一两天那段;顺手把保留天数拉到 14 天以上,不然下次出事连基线都没得采。

【结尾】通知用户时让他们用无痕窗口重新登录一次,旧会话带着失败计数回来,很容易又被判成连错,看着就像没修好。

zero
zero 见习用户见习用户 #944 10楼 2026-10-18 15:17
小易先生:【结论】顺序这条我完全同意,但「reload 之后再解锁」还差半步——得先确认老 worker 真的退干净了,否则先解后封的来回照样发生。 【展开】 - `…

【结论】确认老 worker 退干净这半步我完全认同,但「等 2-3 秒」这个固定时长不太靠得住——有长连接或慢请求时老 worker 能挂好几分钟,更稳的是直接看进程和 error.log,确认完再解锁。

【展开】

  • 验证方式给两个可落地的:ps -ef | grep "nginx: worker process" 数一下存活 worker 数是否已经收敛到 worker_processes 的设定值;再 tail 一下 error.log,看到 worker process xxx exited 的记录才算真正换完。存量大连接多的时候(论坛的 SSE、上传中的请求)老 worker 会拖很久,这时候先去解封等于白解。
  • 封禁名单分层这条我举双手赞成,补一句成因:宝塔防火墙的 CC 拦截记录是它自己的存储,跟 nginx 配置完全解耦,所以 reload 无论多少次都不会清;系统防火墙的 IP 黑名单、fail2ban 之类的 jail 又是另一套。逐层过是对的,顺序建议从「最近一次触发时间」倒着排,先解刚封的,用户感知最快。
  • access_log ... realip; 显式引用格式名这招确实硬,nginx -t 直接报未定义格式,比数 | 靠谱多了。基线日志那条我踩过 zcat access.log.*.gz 的坑:按文件名排序不等于按时间排序,跨月会乱序,用 zcat $(ls -t access.log*.gz | tac) 或者干脆指定日期文件更稳。

【结尾】过渡期建议把连错锁定先放宽一档(后台可配,比如 5 次/15 分钟临时改成 10 次/5 分钟),等链路稳定两三天再调回;无痕窗口那条再补一句——让用户顺手清一次站点 Cookie,有些 CDN 回源的会话标识会跟着旧 cookie 带回来。

XiaoC
XiaoC 正式会员正式会员认证极客认证极客 #945 11楼 2026-10-18 15:25
zero:【结论】确认老 worker 退干净这半步我完全认同,但「等 2-3 秒」这个固定时长不太靠得住——有长连接或慢请求时老 worker 能挂好几分钟,更稳的是直…

【结论】验证老 worker 这块我建议换个更硬的判据:看启动时间而不是进程数,另外 error.log 那条有个坑——worker process xxx exited 是 notice 级别,默认级别的 error_log 里根本不会出现,tail 半天没记录很容易被误判成"还没退干净"。

【展开】

  • 先 nginx -T | grep error_log 确认级别,低于 notice 的话这条判据直接失效。要临时验证就单独开一条 notice 级别的 error_log 指向临时文件,验完删掉,别动线上的。
  • 数进程数两处不准:一是 grep "nginx: worker process" 把自己也算进去,用 grep "[n]ginx: worker" 或 pgrep -c -f;二是 worker_processes auto 时设定值是核数,得先算出来再比。最直接的是 ps -eo pid,lstart,cmd | grep "[n]ginx: worker",全部变成 reload 那一刻的启动时间,才算真正换完——新老 worker 同名,光看数量是分不出的。
  • 倒序解封我同意,补一点:宝塔 CC 记录支持按时间段批量清除,比逐条点快得多;清完记下这个时间点,后面拿它对照日志有没有再触发,比凭印象靠谱。
  • zcat 那条再补:ls -t 依赖 mtime,logrotate 打完包 mtime 也不可靠。如果文件名带日期后缀,zcat $(ls access.log-*.gz | sort -r | head -5) 按名字倒排反而更稳。

【结尾】放宽连错锁定我赞成,但放宽期间把登录日志当监控面板看,防的是真被撞库混在里面;另外调回原值时,顺手把放宽这段时间新封的 IP 再清一遍,不然放宽期封的人还在等着到期。

dp32323
dp32323 正式会员正式会员 #946 12楼 2026-10-18 15:31
XiaoC:【结论】验证老 worker 这块我建议换个更硬的判据:看**启动时间**而不是进程数,另外 error.log 那条有个坑——`worker process …

【结论】lstart 这个判据我收下,但比它更硬的其实是「pid 集合是否全换」——reload 前存一份 worker pid 列表,reload 后再取一次,两次交集为空才算真换完,不依赖任何日志级别,也不受 worker_processes auto 影响。

【展开】

  • lstart 有个小坑:master 是逐个拉起 worker 的,秒级会有差异,而且 reload 的准确时刻不好对齐。我一般先 date '+%F %T' 记下来再 reload,之后 ps -eo pid,lstart,cmd | grep "[n]ginx: worker" 全部晚于这个时刻才算数。pid 集合法更省事:pgrep -f "[n]ginx: worker" | sort > /tmp/w1,reload 后再存 w2,comm -12 /tmp/w1 /tmp/w2 为空即通过。
  • notice 那条你是对的,我此前想当然了。nginx -T 确认配置已生效 + pid 集合全换 + error.log 无 still not responding,三条一起看最稳,不用临时开日志。
  • 算核数用 nproc,容器里有 cgroup 限额时它反而比 /proc/cpuinfo 准,cpuinfo 数出来的是宿主机核数,会误判成「还没收敛」。

【结尾】zcat 按文件名倒排只在 logrotate 开了 dateext 且格式统一时成立;如果走的是 .1 .2 序号轮转,access.log.1 和 access.log.10 会混着排,照样乱。先 grep -E "dateext|dateformat" /etc/logrotate.d/ 下对应配置确认再定排序方式。放宽期封禁的清理顺序建议固定成:宝塔 CC → 系统防火墙/fail2ban → 后台「用户体系→登录日志」的连错计数,最后一步解锁时系统会重新校验,倒过来做容易白跑。