LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

还在手动封IP?Nginx动态封禁IP实战指南,三种方案搞定恶意访问

admin
2026年2月13日 17:14 本文热度 67

前言

服务器遭受恶意IP访问、CC攻击、爬虫骚扰是每个运维和开发都会遇到的问题。当你的服务器日志里出现成千上万条恶意请求,手动一个个封禁IP显然不现实。

本文将详细介绍三种主流的Nginx动态封禁IP方案,帮助你根据实际场景选择最适合的解决方案。


一、为什么需要动态封禁IP?

常见攻击场景

  • • CC攻击:大量请求持续压垮服务器
  • • 暴力破解:SSH、后台登录尝试
  • • 恶意爬虫:抓取数据消耗服务器资源
  • • DDoS攻击:分布式拒绝服务

静态封禁的痛点

传统方式修改nginx.conf添加deny指令,需要reload配置,效率低下且无法应对大规模攻击。

动态封禁的优势

  • • ✅ 无需重启Nginx
  • • ✅ 自动识别攻击
  • • ✅ 支持分布式部署
  • • ✅ 灵活的封禁策略

二、方案全景图

方案架构对比

三大方案对比

方案
性能
复杂度
扩展性
适用场景
Nginx内置模块
★★★★
★★
小型网站、快速部署
Fail2ban
★★★
★★★
★★
单机防护、暴力破解
Lua+Redis
★★★★★
★★★★
★★★★★
高并发、分布式生产环境

三、方案一:Nginx内置模块(快速上手)

3.1 核心模块介绍

Nginx内置三个强大的访问控制模块:

模块
功能
适用场景
ngx_http_access_module
IP黑白名单
简单IP控制
ngx_http_limit_req_module
请求速率限制
防止请求洪水
ngx_http_limit_conn_module
连接数限制
防止连接耗尽

3.2 请求速率限制

工作原理图

限流原理图

Nginx使用漏桶算法实现限流,请求以恒定速率被处理,超过速率的请求会被缓冲或拒绝。

配置示例

限流配置示例

基础配置

http {
    # 定义限流区域:每秒10个请求

    limit_req_zone
 $binary_remote_addr zone=general:10m rate=10r/s;

    # API接口更宽松:每秒30个请求

    limit_req_zone
 $binary_remote_addr zone=api:10m rate=30r/s;

    server
 {
        # 普通接口限流

        location
 / {
            limit_req
 zone=general burst=20 nodelay;
        }

        # API接口限流,允许突发

        location
 /api/ {
            limit_req
 zone=api burst=50 nodelay;
        }

        # 登录接口严格限流

        location
 /login {
            limit_req
 zone=general burst=5 nodelay;
        }
    }
}

参数说明

  • • rate:请求速率(r/s=每秒,r/m=每分钟)
  • • burst:突发缓冲区大小
  • • nodelay:立即处理突发请求(不加则排队)

3.3 连接数限制

http {
    # 每个IP最多10个并发连接

    limit_conn_zone
 $binary_remote_addr zone=addr:10m;

    server
 {
        limit_conn
 addr 10;

        location
 /download/ {
            # 下载资源限制为5个连接

            limit_conn
 addr 5;
        }
    }
}

3.4 IP黑白名单

server {
    # 黑名单方式

    deny
 192.168.1.100;
    deny
 10.0.0.0/8;

    # 白名单方式

    allow
 192.168.1.0/24;
    deny
 all;

    # 针对特定路径

    location
 /admin/ {
        allow
 192.168.1.0/24;
        deny
 all;
    }
}

3.5 优缺点分析

优点

  • • 无需安装额外组件
  • • 配置简单,易于理解
  • • 性能开销极小

缺点

  • • 修改IP需要reload
  • • 不支持动态更新
  • • 无法多服务器共享

四、方案二:Fail2ban + Nginx(自动防护)

4.1 工作原理

Fail2ban流程图

Fail2ban通过监控Nginx日志,自动识别恶意IP并调用iptables进行封禁。

核心流程

  1. 1. 读取Nginx访问日志
  2. 2. 使用正则表达式匹配攻击特征
  3. 3. 统计IP访问频率
  4. 4. 超过阈值则自动封禁
  5. 5. 定时解封(可配置)

4.2 安装配置

安装Fail2ban

# Ubuntu/Debian
sudo
 apt update
sudo
 apt install fail2ban

# CentOS/RHEL

sudo
 yum install epel-release
sudo
 yum install fail2ban

# macOS

brew install fail2ban

配置Nginx日志格式

http {
    log_format
 jail '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '

                    '"$http_referer" "$http_user_agent"'
;

    access_log
 /var/log/nginx/access.log jail;
}

创建Filter过滤器

创建/etc/fail2ban/filter.d/nginx-req-limit.conf

[Definition]
failregex
 = limiting requests, excess:.* by zone.*client: <HOST>
            <HOST> - .* - .* ".*(login|wp-admin|admin).*" (401|403|404)
ignoreregex =

创建Jail监狱规则

创建/etc/fail2ban/jail.d/nginx.conf

[nginx-req-limit]
enabled
 = true
filter
 = nginx-req-limit
action
 = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath
 = /var/log/nginx/*access.log
maxretry
 = 10
findtime
 = 60
bantime
 = 3600

参数说明

  • • maxretry:最大失败次数
  • • findtime:检测时间窗口(秒)
  • • bantime:封禁时长(秒)

4.3 常用防护场景

防护暴力破解

[nginx-auth]
enabled
 = true
filter
 = nginx-auth
action
 = iptables-multiport[name=NoAuth, port="http,https"]
logpath
 = /var/log/nginx/*error.log
maxretry
 = 5
findtime
 = 300
bantime
 = 1800

防护扫描攻击

[nginx-noscript]
enabled
 = true
filter
 = nginx-noscript
action
 = iptables-multiport[name=NoScript, port="http,https"]
logpath
 = /var/log/nginx/*access.log
maxretry
 = 6
findtime
 = 60
bantime
 = 86400

4.4 管理命令

# 启动服务
sudo
 systemctl start fail2ban
sudo
 systemctl enable fail2ban

# 查看状态

sudo
 fail2ban-client status

# 查看特定jail状态

sudo
 fail2ban-client status nginx-req-limit

# 手动封禁IP

sudo
 fail2ban-client set nginx-req-limit banip 1.2.3.4

# 手动解封IP

sudo
 fail2ban-client set nginx-req-limit unbanip 1.2.3.4

# 查看被封禁的IP

sudo
 iptables -L -n | grep REJECT

4.5 优缺点分析

优点

  • • 自动识别攻击,无需人工干预
  • • 配置灵活,规则可自定义
  • • 社区成熟,文档丰富

缺点

  • • 依赖iptables(容器环境可能有问题)
  • • 只能防护单机
  • • 日志分析有性能开销

五、方案三:Lua + Redis(生产推荐)

5.1 架构设计

Lua+Redis流程图

这是目前生产环境最流行的方案,结合了OpenResty的高性能和Redis的共享存储。

核心优势

  • • 毫秒级响应速度
  • • 支持分布式部署
  • • 动态管理,无需重启
  • • 丰富的管理接口

5.2 环境准备

安装OpenResty

# CentOS/RHEL
wget https://openresty.org/package/centos/openresty.repo
sudo
 mv openresty.repo /etc/yum.repos.d/openresty.repo
sudo
 yum install -y openresty

# Ubuntu/Debian

wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
sudo
 add-apt-repository -y "deb http://openresty.org/package/ubuntu/ $(lsb_release -sc) openresty"
sudo
 apt update
sudo
 apt install -y openresty

# macOS

brew install openresty

安装Redis

# Ubuntu/Debian
sudo
 apt install redis-server

# CentOS/RHEL

sudo
 yum install redis

# macOS

brew install redis

# 启动Redis

sudo
 systemctl start redis

5.3 核心Lua脚本

access.lua(访问检测脚本)

local redis = require "resty.redis"
local
 cjson = require "cjson"

-- Redis连接配置

local
 red = redis:new()
red:set_timeout(1000) -- 1秒超时

local
 ok, err = red:connect("127.0.0.1", 6379)
if
 not ok then
    ngx.log(ngx.ERR, "failed to connect: ", err)
    return

end


-- 获取客户端IP

local
 client_ip = ngx.var.remote_addr
local
 blacklist_key = "blacklist:" .. client_ip

-- 检查IP是否在黑名单

local
 is_banned, err = red:get(blacklist_key)
if
 is_banned == 1 then
    -- 获取封禁原因

    local
 reason, err = red:get(blacklist_key .. ":reason")
    ngx.header["X-Ban-Reason"] = reason or "Unknown"
    ngx.status = ngx.HTTP_FORBIDDEN
    ngx.say("IP被封禁,请联系管理员")
    ngx.exit(ngx.HTTP_FORBIDDEN)
end


-- 记录访问频率(可选)

local
 access_key = "access:" .. client_ip
local
 count, err = red:incr(access_key)
if
 count == 1 then
    red:expire(access_key, 60) -- 60秒窗口
end


-- 如果60秒内访问超过100次,自动加入黑名单

if
 count > 100 then
    red:set(blacklist_key, 1)
    red:expire(blacklist_key, 3600) -- 封禁1小时
    red:set(blacklist_key .. ":reason", "高频访问")
    ngx.log(ngx.WARN, "Auto-banned IP: ", client_ip)
end


-- 保持连接池

local
 ok, err = red:set_keepalive(10000, 100)
if
 not ok then
    ngx.log(ngx.ERR, "failed to set keepalive: ", err)
end

5.4 Nginx配置

# /usr/local/openresty/nginx/conf/nginx.conf

http
 {
    # 引入Redis库

    lua_package_path
 "/usr/local/openresty/lualib/?.lua;;";

    # 共享内存配置

    lua_shared_dict
 ban_dict 10m;

    init_by_lua_block
 {
        -- 初始化时加载配置
    }

    server
 {
        listen
 80;
        server_name
 example.com;

        # 在access阶段执行Lua脚本

        access_by_lua_block
 {
            require("access").run()
        }

        location
 / {
            root
 /var/www/html;
            index
 index.html;
        }

        # 管理接口(需要IP白名单保护)

        location
 /admin/ban/ {
            allow
 192.168.1.0/24;
            deny
 all;

            content_by_lua_block
 {
                local
 redis = require "resty.redis"
                local red = redis:new()
                red:connect("127.0.0.1", 6379)

                local uri = ngx.var.request_uri
                local method = ngx.req.get_method()

                -- 添加黑名单
                if method == "POST" and uri:match("/admin/ban/add") then
                    ngx.req.read_body()
                    local body = ngx.req.get_body_data()
                    local data = cjson.decode(body)

                    red:set("blacklist:" .. data.ip, 1)
                    red:expire("blacklist:" .. data.ip, data.ttl or 3600)
                    red:set("blacklist:" .. data.ip .. ":reason", data.reason or "Manual ban")

                    ngx.say("OK")
                end

                -- 移除黑名单
                if method == "POST" and uri:match("/admin/ban/remove") then
                    ngx.req.read_body()
                    local body = ngx.req.get_body_data()
                    local data = cjson.decode(body)

                    red:del("blacklist:" .. data.ip)
                    red:del("blacklist:" .. data.ip .. ":reason")

                    ngx.say("OK")
                end

                -- 查看黑名单列表
                if method == "GET" and uri:match("/admin/ban/list") then
                    local keys = red:keys("blacklist:*")
                    ngx.say(cjson.encode(keys))
                end
            }
        }
    }
}

5.5 管理脚本

ban_manager.py(Python管理工具)

#!/usr/bin/env python3
import
 redis
import
 json
import
 sys

class
 BanManager:
    def
 __init__(self, host='127.0.0.1', port=6379, db=0):
        self
.r = redis.Redis(host=host, port=port, db=db, decode_responses=True)

    def
 add_ban(self, ip, ttl=3600, reason="Manual ban"):
        """添加IP到黑名单"""

        key = f"blacklist:{ip}"
        self
.r.setex(key, ttl, 1)
        self
.r.setex(f"{key}:reason", ttl, reason)
        print
(f"✓ 已封禁 {ip},时长 {ttl}秒,原因: {reason}")

    def
 remove_ban(self, ip):
        """从黑名单移除IP"""

        key = f"blacklist:{ip}"
        self
.r.delete(key, f"{key}:reason")
        print
(f"✓ 已解封 {ip}")

    def
 list_bans(self):
        """列出所有被封禁的IP"""

        keys = self.r.keys("blacklist:*")
        bans = []
        for
 key in keys:
            if
 not key.endswith(":reason"):
                ip = key.replace("blacklist:", "")
                ttl = self.r.ttl(key)
                reason = self.r.get(f"{key}:reason") or "Unknown"
                bans.append({
                    'ip'
: ip,
                    'ttl'
: ttl,
                    'reason'
: reason
                })
        return
 bans

    def
 check_ban(self, ip):
        """检查IP是否被封禁"""

        key = f"blacklist:{ip}"
        if
 self.r.exists(key):
            ttl = self.r.ttl(key)
            reason = self.r.get(f"{key}:reason") or "Unknown"
            return
 True, ttl, reason
        return
 False, 0, None

if
 __name__ == "__main__":
    manager = BanManager()

    if
 len(sys.argv) < 2:
        print
("用法:")
        print
("  封禁IP: python ban_manager.py add <IP> [时长] [原因]")
        print
("  解封IP: python ban_manager.py remove <IP>")
        print
("  查看列表: python ban_manager.py list")
        print
("  检查IP: python ban_manager.py check <IP>")
        sys.exit(1)

    command = sys.argv[1]

    if
 command == "add" and len(sys.argv) >= 3:
        ip = sys.argv[2]
        ttl = int(sys.argv[3]) if len(sys.argv) > 3 else 3600
        reason = sys.argv[4] if len(sys.argv) > 4 else "Manual ban"
        manager.add_ban(ip, ttl, reason)

    elif
 command == "remove" and len(sys.argv) >= 3:
        ip = sys.argv[2]
        manager.remove_ban(ip)

    elif
 command == "list":
        bans = manager.list_bans()
        print
(f"\n当前被封禁的IP(共 {len(bans)} 个):")
        print
("-" * 60)
        for
 ban in bans:
            print
(f"IP: {ban['ip']:15} | 剩余: {ban['ttl']:4}秒 | 原因: {ban['reason']}")

    elif
 command == "check" and len(sys.argv) >= 3:
        ip = sys.argv[2]
        is_banned, ttl, reason = manager.check_ban(ip)
        if
 is_banned:
            print
(f"✓ {ip} 已被封禁")
            print
(f"  剩余时间: {ttl}秒")
            print
(f"  封禁原因: {reason}")
        else
:
            print
(f"✓ {ip} 未被封禁")

5.6 高级特性

自动封禁策略

-- 在access.lua中添加

-- 检测404错误过多

local
 status_key = "status:404:" .. client_ip
local
 status_count = red:incr(status_key)
if
 status_count == 1 then
    red:expire(status_key, 300) -- 5分钟窗口
end

if
 status_count > 20 then
    red:set(blacklist_key, 1)
    red:expire(blacklist_key, 7200)
    red:set(blacklist_key .. ":reason", "过多404请求")
end


-- 检测User-Agent异常

local
 user_agent = ngx.var.http_user_agent or ""
if
 user_agent == "" or user_agent:match("bot") or user_agent:match("spider") then
    -- 可疑UA,记录但不封禁

    red:incr("suspicious:ua:" .. client_ip)
end

白名单机制

-- 白名单IP检查
local
 whitelist_key = "whitelist:" .. client_ip
if
 red:exists(whitelist_key) then
    -- 白名单IP,跳过所有检查

    return

end

分布式支持

-- Redis集群支持
local
 red = redis:new()
red:set_timeout(1000)

local
 ok, err = red:connect("redis-cluster.example.com", 6379)
if
 not ok then
    -- 备用Redis

    ok, err = red:connect("redis-backup.example.com", 6379)
end

5.7 性能优化

# 使用连接池
lua_resty_redis_max_idle_timeout
 10000
lua_resty_redis_pool_size 100

# 本地缓存减少Redis查询

lua_shared_dict ip_blacklist_cache 10m;  -- 缓存黑名单

# 定时同步黑名单到本地缓存

init_worker_by_lua_block
 {
    local
 delay = 5  -- 同步间隔(秒)
    local new_timer = ngx.timer.at
    local check = nil

    check = function(premature)
        if not premature then
            -- 从Redis加载黑名单到共享内存
            local redis = require "resty.redis"
            local red = redis:new()
            red:connect("127.0.0.1", 6379)

            local keys = red:keys("blacklist:*")
            local ban_dict = ngx.shared.ban_dict

            for _, key in ipairs(keys) do
                local ip = key:gsub("blacklist:", "")
                ban_dict:set(ip, true)
            end

            new_timer(delay, check)
        end
    end

    new_timer(delay, check)
}

5.8 优缺点分析

优点

  • • 性能极佳,毫秒级响应
  • • 支持分布式部署
  • • 动态管理,无需重启
  • • 功能强大,易于扩展

缺点

  • • 部署复杂度较高
  • • 需要额外的Redis服务
  • • 需要学习Lua编程

六、方案选型建议

决策树

方案选型决策树

组合使用方案

多层防御架构

实战推荐组合

  1. 1. 基础防护:Nginx内置限流(limit_req)
  2. 2. 自动识别:Fail2ban检测攻击
  3. 3. 动态封禁:Lua+Redis统一管理

这种多层防御架构可以有效应对各种攻击场景。


七、生产环境最佳实践

7.1 监控告警

# 监控黑名单数量变化
def
 monitor_blacklist():
    while
 True:
        bans = manager.list_bans()
        if
 len(bans) > 100:
            # 发送告警

            send_alert(f"黑名单IP数量异常: {len(bans)}")
        time.sleep(60)

7.2 定期审查

# 定期导出黑名单报告
crontab -e
0 6 * * * /usr/bin/python3 /scripts/ban_report.py

7.3 应急预案

准备快速封禁和解封脚本:

#!/bin/bash
# emergency_ban.sh - 紧急封禁脚本


IP=$1
REASON=${2:-"Emergency ban"}
TTL=${3:-86400}

curl -X POST http://localhost/admin/ban/add \
  -H "Content-Type: application/json" \
  -d "{\"ip\": \"$IP\", \"ttl\": $TTL, \"reason\": \"$REASON\"}"

echo
 "紧急封禁: $IP"

7.4 日志分析

# 分析封禁效果
awk '$9 == 403 {print $1}' /var/log/nginx/access.log | \
  sort
 | uniq -c | sort -rn | head -20

八、常见问题FAQ

Q1: 容器环境下如何使用Fail2ban?

A: Docker容器中需要使用--cap-add=NET_ADMIN权限,或使用host网络模式。

Q2: Redis宕机会影响服务吗?

A: 不会。Lua脚本中Redis连接失败应该直接放行,避免影响正常用户。

Q3: 如何处理CDN/代理IP?

A: 使用$http_x_forwarded_for获取真实IP:

set_real_ip_from 192.168.1.0/24;  # CDN IP段
real_ip_header
 X-Forwarded-For;

Q4: 封禁时间设置多久合适?

A: 根据攻击类型:

  • • 暴力破解:1-24小时
  • • CC攻击:1-7天
  • • 恶意爬虫:7-30天
  • • 严重攻击:永久封禁

阅读原文:https://mp.weixin.qq.com/s/7SQpmYXrUtiIuIXx0Y34UQ


该文章在 2026/2/13 17:14:36 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved