做站长这些年,最怕的不是没流量,而是流量突然暴涨——尤其是那种来自某个东南亚 IP 段、User-Agent 写着 Python-requests 的“假流量”。上个月我一个图片站就被爬虫盯上了,带宽跑满,正常用户打开页面转圈,监控告警响个不停。当时第一反应是上防盗链,结果配完 valid_referers 发现根本拦不住,因为对方直接伪造 Referer,照样薅得飞起。
这篇文章,笔者就把自己踩过的坑和最终跑通的方案完整梳理一遍。如果你也遇到类似“Nginx 限流配置不生效”“防盗链拦不住爬虫”“IP 封了又换”的问题,下面这几个高频疑问,应该能帮你省下不少排查时间。
先搞清楚:你遇到的到底是哪类攻击?
💡 推荐阅读:Linux 磁盘 100% 别急着删库!老站长手把手教你揪出“空间刺客”并优雅善后
在动手改配置之前,站长建议你先花五分钟看日志。执行下面这条命令,按 IP 统计访问量:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
如果某个 IP 或 /24 段访问量是正常用户的几十倍,且请求路径高度集中(比如全是 /uploads/ 下的图片),那基本就是恶意爬虫或盗链。这时候再针对性地上防盗链 + 限流,才不会误伤正常用户和搜索引擎蜘蛛。
高频疑问 Q&A:这些坑我替你踩过了
💡 延伸阅读:CPU 飙到 100% 别急着重启!一文讲透云服务器高负载的排查与根治
Q1:防盗链配了 valid_referers,为什么图片还是被外站盗用?
这是最典型的“配了等于没配”。很多教程只教你写 valid_referers none blocked *.yourdomain.com;,但漏了关键一步:必须配合 if 判断返回 403 或重定向。更麻烦的是,如果对方用服务端请求(curl、Python 脚本),Referer 可以随便伪造,valid_referers 只对浏览器环境有效。
所以防盗链要分层:第一层挡普通外站引用,第二层用 User-Agent 和频率限制挡脚本。
Q2:limit_req 限流开了,为什么某个 IP 还是能刷?
最常见的原因是 limit_req_zone 的 key 选错了。如果你写的是 $remote_addr,但网站前面套了 CDN 或反向代理,那所有请求的 $remote_addr 都是 CDN 节点 IP,限流自然失效。正确做法是用 $http_x_forwarded_for 或 $binary_remote_addr 配合 real_ip 模块还原真实 IP。
另一个坑是 burst 和 nodelay 参数没调好。burst=5 nodelay 意味着瞬间允许 5 个并发,如果爬虫就是 5 个并发慢速刷,你根本拦不住。这时候要把 burst 调小,甚至为 0。
Q3:封了一批 IP,对方换一批继续刷,怎么办?
手动封 IP 是治标不治本。笔者现在的做法是动态封禁 + 行为分析:用 Nginx 的 map 指令结合 limit_req 的日志,把触发限流的 IP 自动加入黑名单。更狠一点,直接上 ngx_http_limit_conn 限制单 IP 并发连接数,爬虫再换 IP,成本也会高到放弃。
Q4:limit_conn 和 limit_req 到底该用哪个?
简单说:limit_req 管“请求速率”,limit_conn 管“并发连接数”。爬虫通常两种特征都有:高频请求 + 大量并发。笔者建议两个都开,形成组合拳。下面表格是两者的核心区别:
| 模块 | 作用 | 适用场景 |
|---|---|---|
limit_req |
限制单位时间请求数 | 防高频刷接口、防 CC 攻击 |
limit_conn |
限制单 IP 并发连接数 | 防慢速攻击、防爬虫多线程抓取 |
根因剖析:为什么你的防护总被绕过?
💡 深度技术指南:云服务器选型横评:个人博客与小型团队项目到底需要什么配置?
笔者总结下来,核心就三点:
- 真实 IP 没还原:CDN 或代理后面,
$remote_addr是假的,所有基于 IP 的限制全部失效。 - 只防浏览器不防脚本:防盗链依赖 Referer,但脚本可以伪造;User-Agent 也能随便改。
- 限流参数太宽松:
burst给太大,等于给爬虫留了后门。
所以正确的思路是:先还原真实 IP,再叠加多层限制,最后动态封禁。
实操方案:Nginx 配置直接抄
下面这套配置是笔者目前在用的,图片站和 API 站都跑得很稳。注意:limit_req_zone 和 limit_conn_zone 必须写在 http 块里,不能放在 server 里。
http {
# 1. 还原真实 IP(如果用了 CDN,把 CDN 回源 IP 段填进去)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# 2. 定义限流区域,key 用二进制 IP,节省内存
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
# 3. 定义防盗链白名单
map $http_referer $bad_referer {
default 0;
"~*yourdomain\.com" 0;
"~*google\.com" 0;
"~*baidu\.com" 0;
"" 1; # 空 Referer 视为可疑
}
server {
listen 80;
server_name yourdomain.com;
# 4. 应用限流
limit_req zone=req_per_ip burst=3 nodelay;
limit_conn conn_per_ip 5;
limit_req_status 429;
limit_conn_status 429;
# 5. 防盗链 + 可疑 UA 拦截
location ~* \.(jpg|jpeg|png|gif|webp|mp4)$ {
if ($bad_referer) {
return 403;
}
if ($http_user_agent ~* (python|curl|wget|scrapy|httpclient)) {
return 403;
}
root /var/www/uploads;
}
# 6. 动态封禁:把触发限流的 IP 记到日志,后续用脚本拉黑
location / {
access_log /var/log/nginx/access.log;
proxy_pass http://backend;
}
}
}
几个关键点解释一下:burst=3 nodelay 表示瞬间最多允许 3 个请求排队,超过直接 429,对正常用户几乎无感,但爬虫会立刻撞墙。limit_conn 5 限制单 IP 最多 5 个并发连接,多线程爬虫直接废掉。空 Referer 被标记为可疑,是因为正常浏览器访问图片通常会带 Referer,而脚本往往不带。
预防建议:别等被刷爆才动手
- 日志监控要常态化:每天扫一眼访问量 Top 20 的 IP,发现异常立刻处理。可以用
goaccess做实时可视化。 - CDN 和 WAF 该上就上:如果预算允许,套一层带 WAF 的 CDN,能挡掉大部分低级爬虫。选云服务器时优先挑带基础 DDoS 防护的厂商,别贪便宜买那种超售严重的小厂机器,被刷的时候连 SSH 都登不上去。
- 域名和备案要正规:有些站长图省事用免费域名或未备案域名,结果被攻击时连投诉渠道都没有。正规域名 + 备案,至少能在被刷时找服务商协助封禁。
- 定期压测自己的限流规则:用
ab或wrk模拟高频请求,看看 429 是否按预期返回。别等真被攻击了才发现配置写错。
最后说句实在的,Nginx 防护是“够用就好”,没有一劳永逸的方案。爬虫也在进化,今天封了 Python-requests,明天人家就换 Go 的 http.Client。但只要你把真实 IP 还原、限流参数调紧、防盗链和 UA 拦截叠加上,95% 的恶意流量都能挡在门外。剩下的,交给日志监控和动态封禁脚本去兜底。
如果你正在选服务器,记住一句话:防护能力是买服务器时最容易被忽略、但被攻击时最值钱的参数。别等带宽被刷爆了才后悔没选带高防的机型。