网关层是安全防线的最佳落点:所有流量必经 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; # 响应发送超时
}
| 攻击方式 | 防护参数 |
|---|---|
| Slowloris | client_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-Options | MIME 类型嗅探 |
X-Frame-Options | 点击劫持 |
Referrer-Policy | 防止 Referrer 泄露 |
Content-Security-Policy | XSS 与注入面收敛 |
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 核心配置结构 — if/map 与指令上下文
- Nginx 反向代理与负载均衡 — 被限流请求的转发路径
- Nginx HTTPS 与 TLS 加固 — 加密层与安全头联动
- Nginx 缓存与压缩优化 — 缓存节点的限流协同
- Nginx 日志分析与性能调优 — 限流日志与告警分析
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。