Nginx 限流与安全防护:limit_req、白名单与防爬实践

深入讲解限流与安全防护。涵盖 limit_req 突发处理、limit_conn 连接限制、黑白名单、防爬拦截与动态封禁。

网关层是安全防线的最佳落点:所有流量必经 Nginx,在这里限流、拦截与封禁,成本最低、效果最直接。但限流配置一旦拍脑袋,会出现误伤正常用户、burst 处理不当导致请求大量丢弃、白名单顺序错误把内部访问也挡掉等问题。本文围绕 limit_req 与 limit_conn 两类限流,讲解黑白名单、恶意爬虫拦截、慢速攻击防护与安全响应头,并给出基于日志的动态封禁方案。

核心认知:限流的本质是用"可控的排队与丢弃"换取"后端的稳定"。安全防护的本质是"先识别、后处置"——识别靠日志与变量,处置靠 return/limit 指令。

1. limit_req 请求限流

1.1 限流区域定义

# 在 http 上下文定义共享内存区域
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=10r/s;
参数含义
$binary_remote_addr按客户端 IP 限流
zone=req_zone:10m区域名与共享内存大小
rate=10r/s每秒 10 个请求(可用 r/m)

1.2 应用限流与突发

location /api/ {
    limit_req zone=req_zone burst=20 nodelay;
    proxy_pass http://backend;
}
配置行为
无 burst超速立即 503
burst=20允许 20 个请求进入队列排队
burst=20 nodelay突发立即放行,但计入总量,超限才丢
# 基础限流示例
limit_req zone=req_zone burst=5;
limit_req_status 429;             # 自定义返回状态码
limit_req_log_level warn;         # 超限写入日志级别

避坑:nodelay 会放行整段突发,适合对"瞬时尖峰"敏感的业务;没有 nodelay 时突发请求会排入队列,可能整体拉长响应时间。需按业务容忍度选择。

2. limit_conn 连接数限制

2.1 连接数区域

limit_conn_zone $binary_remote_addr zone=conn_zone:10m;

server {
    limit_conn conn_zone 10;          # 每 IP 最多 10 个并发连接
    limit_conn_status 429;
}
维度限制对象
$binary_remote_addr按客户端 IP
$server_name按虚拟主机
$http_x_forwarded_for按真实 IP(需可信代理)

2.2 配合 keepalive 使用

http {
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    server {
        keepalive_timeout 20;
        limit_conn perip 10;          # 含长连接数,注意别设太小
    }
}

核心认知:limit_req 限制的是"单位时间请求数",limit_conn 限制的是"同时建立的连接数"。二者互补——一个挡频率,一个挡并发占用。

3. 黑白名单与访问控制

3.1 allow 与 deny 基础

location /admin/ {
    allow 10.0.0.0/8;                # 内网放行
    allow 203.0.113.0/24;
    deny  all;                       # 其余拒绝
}

3.2 geo 模块动态名单

geo $whitelist {
    default 0;
    10.0.0.0/8        1;
    203.0.113.0/24    1;
}

server {
    if ($whitelist = 0) {
        return 403;                  # 不在白名单直接拒绝
    }
}
方式优点缺点
allow/deny简单直观静态,改配置需 reload
geo支持网段聚合仍是静态
map可读文件/变量组合不直接执行 deny
外部 Lua动态更新需 OpenResty

3.3 带优先级的访问控制

location /internal/ {
    satisfy any;                     # 满足任一条件即放行
    allow 10.0.0.0/8;               # 内网或
    valid_referers none blocked example.com;  # 合法来源
    deny all;
}

避坑:allow/deny 的匹配顺序是"自上而下,先匹配先生效"。deny all; 必须写在最后,否则会误伤其后的白名单。

4. 恶意爬虫拦截

4.1 基于 UA 的拦截

map $http_user_agent $bad_bot {
    default 0;
    "~*curl|wget|python-requests|Scrapy|AhrefsBot|SemrushBot" 1;
}

server {
    if ($bad_bot) {
        return 403;
    }
}

4.2 爬虫识别与限速

limit_req_zone $http_user_agent zone=bot_zone:10m rate=1r/m;

location / {
    limit_req zone=bot_zone burst=1;      # 对可疑 UA 极低频率
    proxy_pass http://backend;
}
手段说明
UA 黑名单拦截已知爬虫
频率限制对未知 UA 低频限流
验证码对高风险路径二次验证
robots 友好给正常爬虫放行(SEO)
JS 挑战判断是否为真实浏览器

避坑:UA 可被随意伪造,仅靠 UA 拦截会误伤或漏网。更稳的套路是"白名单搜索引擎爬虫 + 黑名单已知恶意 + 其余按频率限流"三层组合。

5. 慢速攻击与超时防护

5.1 客户端超时参数

server {
    client_body_timeout   10s;      # 读请求体超时
    client_header_timeout 10s;      # 读请求头超时
    client_max_body_size  10m;      # 请求体上限
    send_timeout          30s;      # 响应发送超时
}
攻击方式防护参数
Slowlorisclient_header_timeout + 最小头部大小限制
慢速请求体client_body_timeout
无限连接占用limit_conn + 空闲超时

5.2 半连接与空闲保护

server {
    listen 80 backlog=1024;         # 控制 accept 队列
    keepalive_timeout 10;           # 缩短空闲 keepalive
    client_body_buffer_size 128k;   # 限制请求体缓冲
}

核心认知:慢速攻击的本质是"以极慢的速度占用连接与超时窗口"。所有超时参数必须小于后端处理超时,否则攻击者能无限占住连接。

6. 安全响应头与加密策略

6.1 常用安全响应头

add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Content-Security-Policy "default-src 'self'" always;
响应头防护目标
X-Content-Type-OptionsMIME 类型嗅探
X-Frame-Options点击劫持
Referrer-Policy防止 Referrer 泄露
Content-Security-PolicyXSS 与注入面收敛
Permissions-Policy浏览器功能权限

6.2 隐蔽后端信息

server_tokens off;                   # 隐藏 Nginx 版本号
# 上游异常时不透传错误详情
proxy_hide_header X-Powered-By;

避坑:add_header 在继承时存在"同层覆盖"问题——父级定义的头在子级一旦重写同名字段,父级全部 add_header 会失效。公共安全头建议统一放到 server 顶层。

7. 限流观测与动态封禁

7.1 限流日志

log_format limited '$remote_addr - [$time_local] "$request" '
                   '$status $request_length rt=$request_time '
                   'ua="$http_user_agent"';

limit_req_log_level warn;
error_log /var/log/nginx/error.log warn;   # 超限记录在此

7.2 动态封禁脚本

# 统计 5 分钟 429/403 高频 IP 并写入 deny 文件
awk '{print $1}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn \
  | awk '$1>200 {print "deny "$2";"}' > /etc/nginx/conf.d/blocked.conf

nginx -t && nginx -s reload        # 应用封禁

7.3 与 fail2ban 集成

# fail2ban 配置:扫描 Nginx 日志的 429 高频 IP 自动封禁
[nginx-limit-req]
enabled   = true
filter    = nginx-limit-req
action    = iptables-multiport[name=nginx, port="80,443"]
logpath   = /var/log/nginx/error.log
maxretry  = 3
findtime  = 300
bantime   = 3600

核心认知:动态封禁依赖"日志 → 统计 → 配置 → reload"的闭环。把高频 429/403 的 IP 批量写入 deny 文件,比逐个手工封禁高效得多,但仍需设置封禁上限防止误伤。

8. 总结

环节要点
请求限流limit_req_zone + burst/nodelay 控制突发
连接限流limit_conn 挡并发占用
黑白名单allow/deny 顺序敏感,geo 网段聚合
防爬UA 黑名单 + 频率限流 + 验证码三层
慢速攻击客户端超时 < 后端超时
安全头X-Frame/CSP/Referrer-Policy 统一顶层
观测limit_req_log_level + 429 统计
封禁日志闭环动态写入 deny

限流与封禁保证了网关的稳定性与安全性,但这些策略的命中情况、请求耗时与状态码分布,都需要一套完整的日志体系来支撑。下一篇将聚焦 log_format、JSON 日志与访问分析,把安全事件与性能数据变成可观测的信号。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「nginx」更多文章

  1. Nginx Ingress Controller:Kubernetes 云原生网关的路由、证书与金丝雀发布
  2. Nginx 监控与可观测性:stub_status 指标、Prometheus 集成与容量规划
  3. Nginx 静态资源与页面加速:零拷贝、缓存验证与 Brotli 压缩联动