Nginx Referer 防盗链实战:valid_referers 四种请求实测
别人家的页面直接 <img> 标签挂着你的图片,流量账单算在你头上——这就是盗链。HTTP 请求头里的 Referer 记录了请求是从哪个页面跳转来的,Nginx 的 Referer 模块(ngx_http_referer_module)利用这个字段判断请求来源,既能做防盗链,也能把来源信息记进日志做流量分析。
核心机制一句话:valid_referers 列出合法来源,不匹配的请求会让内置变量 $invalid_referer 变成非空,配合 if 拒绝即可。 本文在 nginx:alpine 容器里把带 Referer、不带 Referer、空头、盗链头四种请求各跑一遍,每种都有实拍结果,最后把一个流传很广的错误写法实跑给你看。
实验环境与机制前提
- nginx:alpine 官方镜像,版本 nginx/1.31.5,referer 模块为内置标准模块,无需额外编译参数;
- 请求在容器内自发自收,用 curl 的
-H手工构造 Referer 头。
先说清楚 Referer 的可信度:浏览器从页面 A 跳到页面 B 时,请求 B 资源通常会带上 Referer 头,值就是页面 A 的 URL。但它是客户端提供的,可以直接伪造。所以这个模块的定位是粗粒度的来源控制——挡住顺手挂链接的低成本盗用,不是强安全校验。
模块的三个用途:
- 防盗链:只允许特定来源的页面加载你的图片、视频等资源,其他来源直接拒绝。
- 统计分析:把
$http_referer记进访问日志,分析流量从哪儿来。 - 安全控制:挡掉一批不带合法 Referer 的低质量自动请求。
实例一:最小防盗链配置,四种请求实测
最典型的写法是 valid_referers 配合 if:
1 | server { |
拆开看这条配置在做什么:
valid_referers none blocked yourwebsite.com;定义允许的 Referer 列表:none表示允许不带 Referer 的请求(直接在地址栏输入网址就是这种),blocked表示允许 Referer 头存在但值为空、或被防火墙/代理删掉的请求,最后的域名是合法来源;- 匹配失败的请求,
$invalid_referer的值为1,if命中后直接return 403。
valid_referers 能吃的不止这三样。server_names 表示 Referer 里带着本机 server_name 的请求一律放行;域名可以带 * 通配符(*.example.com、example.* 都合法),还能带 URI 前缀——www.example.org/galleries/ 只放行这个路径下跳来的请求;以 ~ 开头可以写正则,正则匹配的是 http:// 或 https:// 之后的文本。整套匹配大小写不敏感,检查时 Referer 里的端口会被忽略。位置也有讲究:这条指令只能出现在 server 和 location 层级(官方文档口径),写进 http 层直接无效,排查”配置了却不生效”时别漏了这一层。
配置加载后,四种请求各发一次,看响应码:

矩阵很清楚:站内来源放行,盗链来源 403,无来源和空来源都按 none、blocked 的声明放行。none 和 blocked 要不要加,取决于业务——用户会直接打开图片地址就得留 none,前面有会剥 Referer 的隐私代理就得留 blocked,两个都删,正常用户会被误伤。
实例二:blocked 到底挡的是什么
blocked 值得单独跑一次,因为它最容易和 none 混淆。curl 用 -H "Referer;" 发送一个存在但值为空的 Referer 头:

结果是 200。这就是 blocked 的语义:头存在过,但值在中途被剥掉了——企业网关、隐私插件、HTTPS 跳转都会干这事。如果把这种请求判成盗链,从这类环境过来的用户会全体中招。它的分量可以实打实称出来:把 blocked 从名单里删掉再发同一个空头请求,403——一升一降之间,就是 blocked 在兜的那部分正常流量。反过来提醒一句:none 和 blocked 都放行意味着”无来源的请求不设防”,对图片站是合理代价,对付费内容就要掂量了。
实例三:来源统计,把 Referer 记进日志
防盗链是”拒绝”,统计是”记录”。核心是自定义 log_format 把 $http_referer 记下来:
1 | http { |
$http_referer 和 $http_user_agent 分别记录来源页面和浏览器信息;location 里再写一次 access_log 会覆盖 http 级别的设置,把这部分流量单独落到一个文件,方便分开统计。盗链请求被拒的日志行是现成的证据(配图用了 $status "$http_referer" 的精简格式聚焦来源字段,生产上照实例代码块的完整格式配即可):

$http_referer 字段原样记下了 https://evil.example/hotlink。日志攒起来之后用途就很实际:分析广告点击来源、看流量主要从哪些站点跳过来、结合来源调整内容发布策略。
错误写法对比:allow 写域名,nginx -t 直接报错
网上流传的防盗链配置里,常见 if 后面再接访问控制的写法:
1 | valid_referers none blocked yourwebsite.com; |
这份配置加载不了。allow/deny 属于 access 模块,只接受 IP/CIDR(和 unix:、all),不能写域名——referer 模块的域名匹配能力不能移植过去。实跑一下 nginx -t 看报错:

invalid parameter "yourwebsite.com",测试直接失败。如果只是照抄粘贴,服务起不来的同时还会拖累同机其他站点;这也是为什么改完配置永远先 nginx -t 再 reload。
注意事项
none和blocked是不是要加,取决于业务:允许用户直接打开资源就得加none,前面有会剥 Referer 的代理就得加blocked;都不加,等于只认带合法域名的请求。- Referer 可以伪造,防盗链挡的是”顺手挂链接”这种低成本盗用,防不了定向伪造;强校验要走签名 URL(secure_link)或登录态。
- 域名来源多了以后,先用
server_names放行指向自己的跳转,再用*.example.com这类通配符覆盖子域;~正则是最后的手段,规则一多就没人维护得动了。 - 改完用
nginx -t验证,reload 前确认日志目录存在且 Nginx 进程有写权限。
小结
回到开头的盗链场景:流量账单算在你头上,是因为服务器默认信任所有来源。valid_referers 把”合法来源”变成一份白名单,$invalid_referer 把判断结果交还给 if,四种请求的实测矩阵就是这套机制的全部行为。防盗链挡不住有心人,但足够让”顺手挂链接”的人去别家找图——对多数站点,这道粗粒度的门已经够用。
本文由作者 2020-2024 年间的 CSDN 博客文章重构而来,原发布于 CSDN。