Nginx限流与防爬虫配置方案 - 运维工程师实战指南
字数 1937 2025-09-23 19:27:38
Nginx限流与防爬虫配置实战指南
一、核心需求与选型优势
1.1 业务痛点
- 流量突增:正常业务流量暴涨或遭受CC攻击,导致服务器压力过大。
- 恶意爬虫:频繁请求消耗服务器带宽与计算资源,增加成本。
- 数据泄露风险:敏感信息被恶意爬虫批量采集。
- 用户体验下降:正常用户因资源被占用而访问缓慢或失败。
1.2 技术选型:为何选择Nginx?
- 高性能:基于事件驱动模型,能高效处理数万并发连接。
- 低资源消耗:内存占用远低于传统服务器(如Apache)。
- 模块化设计:拥有丰富的官方及第三方模块,扩展性强。
- 配置灵活:支持精细化的规则配置与动态重载,无需重启服务。
二、核心限流算法原理解析
2.1 令牌桶算法 (Token Bucket)
- 核心机制:系统以恒定速率向一个容量固定的“桶”中添加令牌。请求处理需从桶中获取一个令牌。
- 特性:
- 桶满时,新令牌被丢弃。
- 请求到达时,若桶中有令牌则立即处理;若无令牌则请求被限流(拒绝或排队)。
- Nginx实现:
ngx_http_limit_req_module模块基于此算法实现请求速率限制。
2.2 漏桶算法 (Leaky Bucket)
- 核心机制:请求像水一样流入一个容量固定的“桶”,桶底有一个孔,以恒定速率“漏出”请求进行处理。
- 特性:
- 无论请求流入速率多快,处理速率都是固定的。
- 桶满时,新流入的请求会被丢弃。
- 与令牌桶区别:漏桶平滑输出流量,令牌桶允许一定程度的突发流量。
三、基础限流配置实战
3.1 基于IP的请求频率限制
这是最基础的限流方式,在 http 块中定义限流区域(zone),在 server 或 location 块中应用。
http {
# 定义限流区域:基于客户端IP($binary_remote_addr)
# 区域名:ip_limit,分配10MB内存,速率限制:10 requests/second
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
# 定义连接数限制区域:限制单个IP的最大并发连接数
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
server_name example.com;
location / {
# 应用IP限流规则
# burst=5: 允许超过速率限制的5个请求排队等待(突发流量缓冲)
# nodelay: 对于超出 burst 的请求,立即返回错误,不延迟处理
limit_req zone=ip_limit burst=5 nodelay;
# 应用连接数限制:单IP最大并发10个连接
limit_conn conn_limit 10;
# 自定义限流后的HTTP状态码(默认429为Too Many Requests)
limit_req_status 429;
limit_conn_status 429;
proxy_pass http://backend;
}
# 自定义限流错误页面
error_page 429 /429.html;
location = /429.html {
root /var/www/html;
internal; # 标记为内部请求,不允许外部直接访问
}
}
}
3.2 基于URI的差异化限流
针对不同特性的接口或资源,实施不同的限流策略,优化资源利用。
http {
# 针对API接口:限制较严格
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
# 针对静态资源:限制可放宽
limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s;
# 针对登录等敏感接口:限制非常严格,防止爆破
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
server {
listen 80;
server_name api.example.com;
location /api/ {
limit_req zone=api_limit burst=2 nodelay;
proxy_pass http://api_backend;
}
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
limit_req zone=static_limit burst=20;
expires 1d; # 设置浏览器缓存
add_header Cache-Control "public, immutable";
}
location /api/login {
limit_req zone=login_limit burst=1;
# 记录登录限流日志,用于监控和分析
access_log /var/log/nginx/login_limit.log combined;
proxy_pass http://auth_backend;
}
}
}
3.3 基于地理位置的限流
结合 ngx_http_geoip2_module 模块,根据不同国家/地区实施不同的限流策略。
- 安装GeoIP2模块和数据库。
- Nginx配置:
http {
# 加载GeoIP2国家数据库
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 5m; # 每隔5分钟自动重载数据库
$geoip2_data_country_code country iso_code; # 获取国家代码(如CN, US)
$geoip2_data_country_name country names en; # 获取国家英文名
}
# 根据国家代码映射不同的限流速率
map $geoip2_data_country_code $country_limit_rate {
default 10r/s; # 默认速率
CN 20r/s; # 中国用户放宽限制
US 15r/s; # 美国用户
~^(RU|UA)$ 5r/s; # 俄罗斯、乌克兰等地区严格限制(使用正则匹配)
}
# 使用映射后的变量定义限流区域
limit_req_zone $binary_remote_addr zone=country_limit:10m rate=$country_limit_rate;
server {
listen 80;
server_name global.example.com;
location / {
limit_req zone=country_limit burst=5;
# 将国家信息添加到响应头,便于调试(生产环境可移除)
add_header X-Country-Code $geoip2_data_country_code;
add_header X-Country-Name $geoip2_data_country_name;
proxy_pass http://backend;
}
}
}
四、高级防爬虫策略
4.1 User-Agent检测与过滤
通过识别HTTP请求头中的 User-Agent 字段来过滤常见爬虫工具和恶意扫描器。
http {
# 映射规则:判断是否为爬虫
map $http_user_agent $is_crawler {
default 0; # 默认不是爬虫
# 匹配常见爬虫关键词
~*bot 1;
~*spider 1;
~*crawler 1;
~*scraper 1;
# 匹配常见爬虫工具
~*python-requests 1;
~*curl 1;
~*wget 1;
~*scrapy 1;
~*beautifulsoup 1;
# 匹配可疑的空UA或极短UA
"" 1;
~^.{0,10}$ 1;
}
# 映射规则:判断是否为友好的搜索引擎爬虫(白名单)
map $http_user_agent $allowed_crawler {
default 0;
~*googlebot 1;
~*bingbot 1;
~*baiduspider 1;
~*slurp 1; # Yahoo
}
server {
listen 80;
server_name example.com;
location / {
# 组合判断:是爬虫且不在白名单内,则阻止
if ($is_crawler) {
set $block_crawler 1;
}
if ($allowed_crawler) {
set $block_crawler 0;
}
if ($block_crawler) {
return 403; # Forbidden
}
proxy_pass http://backend;
}
# 为友好爬虫提供robots.txt
location /robots.txt {
root /var/www/html;
add_header Cache-Control "public, max-age=3600";
}
}
}
4.2 基于请求特征的智能识别
分析请求中的其他特征,如Referer、Accept头等,识别异常行为。
http {
# 频率检查区域
limit_req_zone $binary_remote_addr zone=freq_check:10m rate=30r/s;
# 检测可疑的Referer(无或特定值)
map $http_referer $suspicious_referer {
default 0;
"" 1; # 无Referer头
"-" 1; # Referer明确设置为短横线
}
# 检测异常的头组合(例如都为空)
map "$http_accept:$http_accept_language:$http_accept_encoding" $suspicious_headers {
default 0;
":::" 1; # 三者均为空
~^[^:]*:[^:]*:$ 1; # Accept-Encoding为空
}
server {
listen 80;
server_name example.com;
location / {
# 风险评估
set $risk_score 0;
if ($suspicious_referer) {
set $risk_score "${risk_score}1";
}
if ($suspicious_headers) {
set $risk_score "${risk_score}1";
}
# 如果同时满足多个可疑条件,则应用更严格的限流并记录日志
if ($risk_score ~ "11") {
access_log /var/log/nginx/suspicious.log combined;
limit_req zone=freq_check burst=1 nodelay; # 极严格的限流
}
proxy_pass http://backend;
}
}
}
4.3 JavaScript挑战验证
对于高度可疑的流量,返回一个简单的JavaScript计算挑战,真实浏览器会自动执行,而很多简单爬虫无法处理。
- 前提:需要安装OpenResty或带Lua模块的Nginx (
ngx_http_lua_module)。
http {
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
# 共享内存字典,用于存储验证通过的状态
lua_shared_dict challenge_cache 10m;
server {
listen 80;
server_name secure.example.com;
# 挑战页面
location /challenge {
content_by_lua_block {
local template = require "resty.template"
-- 生成一个基于时间和IP的挑战码并哈希
local challenge = ngx.var.request_time .. ngx.var.remote_addr
local hash = ngx.encode_base64(ngx.hmac_sha1("secret_key", challenge))
-- 渲染一个包含JS计算的HTML页面
local html = [[
<!DOCTYPE html><html><head><title>Verification Required</title><meta name="robots" content="noindex, nofollow"></head>
<body><h1>Verifying your browser...</h1>
<script>
var result = Math.pow(2, 3) + 5; // 一个简单的计算
var challenge = "{{challenge}}";
setTimeout(function() {
var form = document.createElement('form');
form.method = 'POST';
form.action = '/verify';
var inputC = document.createElement('input'); inputC.type = 'hidden'; inputC.name = 'challenge'; inputC.value = challenge;
var inputA = document.createElement('input'); inputA.type = 'hidden'; inputA.name = 'answer'; inputA.value = result;
form.appendChild(inputC); form.appendChild(inputA);
document.body.appendChild(form); form.submit();
}, 2000);
</script></body></html>
]]
ngx.say(template.compile(html)({challenge = hash}))
}
}
# 验证答案
location /verify {
content_by_lua_block {
ngx.req.read_body()
local args = ngx.req.get_post_args()
-- 检查答案是否正确 (2^3 + 5 = 13)
if args.answer == "13" then
local cache = ngx.shared.challenge_cache
-- 验证通过,将IP存入缓存,有效期1小时
cache:set(ngx.var.remote_addr, "verified", 3600)
ngx.redirect("/") -- 重定向到原始请求
else
ngx.status = 403
ngx.say("Verification failed")
end
}
}
location / {
access_by_lua_block {
local cache = ngx.shared.challenge_cache
-- 检查IP是否已验证
local verified = cache:get(ngx.var.remote_addr)
if not verified then
-- 未验证,重定向到挑战页面
ngx.redirect("/challenge")
end
}
proxy_pass http://backend;
}
}
}
五、动态防护与监控
5.1 实时监控与告警
配置日志格式,记录关键信息,并利用 nginx-module-vts 等模块进行状态监控。
http {
# 自定义安全日志格式
log_format security_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$geoip2_data_country_code';
vhost_traffic_status_zone; # 启用状态监控
server {
listen 80;
server_name example.com;
location / {
access_log /var/log/nginx/security.log security_log;
# 单独记录被限流的请求
if ($limit_req_status = "503") { # 注意:limit_req默认状态码是503
access_log /var/log/nginx/rate_limit.log security_log;
}
proxy_pass http://backend;
}
# Nginx状态监控面板
location /nginx_status {
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
# 限制内部网络访问
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
}
}
}
5.2 自动化黑名单管理
使用Shell脚本定期分析Nginx日志,自动将异常IP加入黑名单。
#!/bin/bash
# auto_blacklist.sh
LOG_FILE="/var/log/nginx/security.log"
BLACKLIST_FILE="/etc/nginx/conf.d/blacklist.conf"
TEMP_FILE="/tmp/nginx_blacklist.tmp"
# 分析当前小时日志,提取异常IP
awk -v date="$(date '+%d/%b/%Y:%H')" '$0 ~ date {
ip = $1
if ($9 == "429" || $9 == "403") { suspicious[ip]++ } # 被限流或拒绝的
if ($10 > 10000) { large_response[ip]++ } # 响应体过大
if ($11 < 0.001) { fast_request[ip]++ } # 请求处理时间极短
total[ip]++
}
END {
for (ip in suspicious) {
# 如果限流次数超100或大响应次数超50,则加入黑名单
if (suspicious[ip] > 100 || large_response[ip] > 50) {
print "deny " ip ";"
}
}
}' $LOG_FILE > $TEMP_FILE
# 如果发现新IP,更新黑名单文件并重载Nginx
if [ -s $TEMP_FILE ]; then
echo "# Auto-generated blacklist - $(date)" > $BLACKLIST_FILE
cat $TEMP_FILE >> $BLACKLIST_FILE
nginx -t && nginx -s reload
echo "Blacklist updated with $(wc -l < $TEMP_FILE) entries"
fi
rm -f $TEMP_FILE
(计划任务) 将此脚本加入crontab,每小时执行一次:
0 * * * * /path/to/auto_blacklist.sh
六、性能优化与最佳实践
6.1 内存使用优化
- 使用
$binary_remote_addr:二进制格式的IP比字符串格式节省更多内存。 - 规范化URI:对相似URI(如带ID的API)进行归一化,减少zone中键的数量。
http {
limit_req_zone $binary_remote_addr zone=main_limit:50m rate=10r/s;
# 映射请求URI,进行规范化(归组)
map $request_uri $normalized_uri {
~^/api/v1/users/([0-9]+) /api/v1/users/:id;
~^/api/v1/products/([0-9]+) /api/v1/products/:id;
~^/static/ /static;
default $request_uri;
}
# 使用规范化后的URI作为限流键的一部分
limit_req_zone "$binary_remote_addr:$normalized_uri" zone=uri_limit:30m rate=20r/s;
server {
location / {
limit_req zone=main_limit burst=10;
limit_req zone=uri_limit burst=5;
proxy_pass http://backend;
# 启用代理缓存,减轻后端压力
proxy_cache my_cache;
proxy_cache_valid 200 1m;
proxy_cache_key "$scheme$proxy_host$normalized_uri";
}
}
}
6.2 配置文件模块化
将配置拆分为多个文件,便于管理和维护。
/etc/nginx/conf.d/rate_limits.conf(基础限流zone定义)/etc/nginx/conf.d/security_maps.conf(各种map映射)/etc/nginx/conf.d/blacklist.conf(动态黑名单)/etc/nginx/maps/malicious_bots.map(专门的恶意UA列表)/etc/nginx/maps/blocked_countries.map(国家代码映射)
在主 nginx.conf 中使用 include 指令引入这些文件。
总结:
本文档涵盖了从Nginx限流防爬虫的原理、基础配置到高级策略的完整方案。核心在于:
- 理解算法:令牌桶与漏桶。
- 分层配置:从IP、URI到地理位置的多维度限流。
- 综合防护:结合UA过滤、请求特征分析、JS挑战等多手段防爬虫。
- 持续运营:通过监控、日志分析和自动化脚本不断完善防护体系。
- 注重性能:优化内存使用和配置结构。
请根据您的实际业务需求和流量特点,灵活调整参数和策略,并进行充分测试后上线。