Nginx 防盗链配置实战:图片与资源保护完整指南

辛辛苦苦写的原创文章,配图被人直接复制链接引用到别的站,流量和带宽都是自己掏钱,对方一分不花还赚了流量,这种滋味相信不少站长都尝过。尤其是图片站、教程站,图片被外站"盗链"是家常便饭:要么是别人整篇文章连图带文一起搬走,要么是论坛、采集站直接引用你的图片地址,高峰期盗链流量甚至能占到总带宽的一半以上。

对付盗链,Nginx 提供了 valid_referers 指令,配合 location 和 if 判断,就能实现"白名单"式防盗链:只有来自你自己网站(以及你允许的搜索引擎、合作伙伴)的请求才能加载图片,其他来源一律拒绝。本文从原理到实战,把配置、进阶玩法和坑都讲清楚。

一、防盗链的基本原理

浏览器加载网页里的图片时,请求头里会带一个 Referer 字段,记录"这个请求是从哪个页面发起的"。比如你在自己网站的文章页里看到一张图,浏览器请求图片时 Referer 就是这篇文章的 URL;如果别人把图片地址贴到他的网站上,访问者在他的页面看到图,浏览器请求图片时 Referer 就变成了他的域名。

防盗链就是检查这个 Referer:valid_referers 指令定义一个允许的 Referer 名单(你自己的域名、搜索引擎、留空等),不在名单里的请求就认为是盗链,返回 403 或者替换成警告图片。

需要说明的是,Referer 是可以伪造的,所以防盗链防的是"君子"不是"小人",但现实中 99% 的盗链都来自直接引用,Referer 方案已经能挡掉绝大部分,性价比极高。

还有两个技术细节值得注意:一是 HTTPS 页面引用 HTTP 资源时不发送 Referer,同源 HTTPS 引用则会发送完整 URL,所以全站启用 HTTPS 后,none(允许空 Referer)这一项依然要保留,否则微信、部分 App 内置浏览器里图片可能显示不出来;二是 Referer 只会暴露"来源页面",不会携带访客身份,防盗链本质上是按"来源"而不是按"人"做判断,理解这一点,配置白名单时就不会纠结了。

二、最基础的防盗链配置

在 Nginx 的 server 块里加上以下配置,保护 jpg、png、gif、webp 等常见图片格式:

location ~* \.(jpg|jpeg|png|gif|webp|bmp|ico|svg)$ {
    valid_referers none blocked
                   server_names
                   *.zz1984.com
                   *.baidu.com *.google.com *.bing.com;
    if ($invalid_referer) {
        return 403;
    }
    expires 30d;
    access_log off;
}

配置说明:

  • none:允许 Referer 为空的请求(直接输入图片地址访问、或某些不发送 Referer 的场景)。一般建议保留,否则有些合法场景会误伤。
  • blocked:允许 Referer 被代理/防火墙剥掉的情况(Referer 以 http:// 或 https:// 开头但内容被去掉)。
  • server_names:允许本站域名(server_name 里配置的域名)。
  • *.zz1984.com:允许所有子域名,也可以写具体的域名。多个域名用空格分隔。
  • *.baidu.com 等:允许搜索引擎抓图,否则搜索引擎会认为图片不可用,影响图片收录。

if ($invalid_referer) 是 Nginx 内置变量,valid_referers 判断不通过时它自动为 1,命中就 return 403。同段配置里顺手加了 expires 30d 让图片走浏览器缓存,access_log off 省掉图片请求的日志量,都是图片场景下的常规优化。

三、用正则匹配更多域名

valid_referers 除了支持通配符 *,还支持正则表达式(以 ~ 开头)。比如想允许所有来自搜索引擎和博客平台的引用:

valid_referers none blocked server_names
               *.zz1984.com
               ~\.baidu\.com ~\.google\.com ~\.bing\.com ~\.so\.com
               ~^https?://(www\.)?weibo\.com/;

注意正则里的点要转义成 \.,结尾用 ; 结束整个指令。正则匹配的是整个 Referer URL,所以写全域名匹配时建议带上 ~^https?:// 前缀锚定,避免误匹配。

四、返回警告图片而不是 403

返回 403 会显示一个难看的错误页,很多站长更喜欢"软处理":盗链请求返回一张写有"图片来自本站"的警告图片,既保住带宽,还顺带做了反向宣传。做法是先放一张警告图,再用 rewrite 把盗链请求指过去:

location ~* \.(jpg|jpeg|png|gif|webp)$ {
    valid_referers none blocked server_names *.zz1984.com;
    if ($invalid_referer) {
        rewrite ^/.*$ /images/anti-hotlink.png last;
    }
    expires 30d;
}

location = /images/anti-hotlink.png {
    root /var/www/zz1984;
}

要注意 rewrite 到 last 后,/images/anti-hotlink.png 会被上面那个图片 location 再次匹配到——它自己带本站 Referer,不会陷入死循环。如果不想用 rewrite,也可以用 return 302 http://你的域名/images/anti-hotlink.png; 跳转,效果类似但多一次请求。

五、其他文件的防盗链

除了图片,视频、音频、压缩包等资源同样可以防盗链,原理一样,把扩展名换掉即可。比如保护 mp4 和 zip:

location ~* \.(mp4|flv|zip|rar|7z|apk)$ {
    valid_referers none blocked server_names *.zz1984.com;
    if ($invalid_referer) {
        return 403;
    }
    limit_rate 512k;   # 顺带给大文件限速,防止恶意下载占满带宽
}

如果只想保护某个目录,比如 uploads 目录,可以这样写:

location /usr/uploads/ {
    valid_referers none blocked server_names *.zz1984.com;
    if ($invalid_referer) {
        return 403;
    }
}

六、配合 CDN 的注意事项

网站接 CDN 之后,访客请求图片时 Referer 来自 CDN 节点还是原始 Referer,取决于 CDN 回源时是否透传。大部分 CDN(Cloudflare、腾讯云 CDN、阿里云 CDN)默认会透传原始 Referer,但也有例外:

  • CDN 回源带上了自己的 Referer:此时 valid_referers 需要把 CDN 的回源域名加进白名单,否则会出现"CDN 节点访问源站图片全部 403"的诡异问题——网页上图片裂了,但直接访问源站图片又是好的。
  • 开启 CDN 的 Referer 黑白名单功能:很多 CDN 自带防盗链配置,直接在 CDN 控制台设置即可,效果比源站设置更省流量(请求在边缘节点就被拦了),源站 Nginx 配置可以保留一份兜底。

排错思路:如果加了防盗链后自己的网站图片也打不开,先用 curl -I 带 Referer 和不带 Referer 各测一次,对比响应码,基本就能定位是 CDN 回源问题还是配置问题。

七、secure_link:带时效的高级防盗链

Referer 防盗链对直接引用很有效,但挡不住两类人:一是伪造 Referer 的,二是把你的图片地址拿到自己站上当"原图"使用的。如果你提供的是付费下载、私有文件这类敏感资源,就需要更严格的方案——Nginx 的 secure_link 模块(ngx_http_secure_link_module,默认编译进主流发行版 Nginx)实现"带签名、带时效"的防盗链:URL 里带一个 MD5 签名和过期时间戳,签名错误或已过期都无法访问。

先看服务端如何生成带签名的 URL。假设密钥是 mysecret,文件路径是 /files/ebook.zip,允许访问到 2026-08-11 24:00(时间戳 1786319999):

#!/bin/bash
# 生成 secure_link URL
URI="/files/ebook.zip"
EXPIRE=1786319999
MD5=$(echo -n "mysecret${URI}${EXPIRE}" | md5sum | awk '{print $1}')
echo "https://www.zz1984.com${URI}?md5=${MD5}&expires=${EXPIRE}"

Nginx 侧配置对应校验:

location /files/ {
    secure_link $arg_md5,$arg_expires;
    secure_link_md5 "mysecret$uri$expire";

    if ($secure_link = "") {
        return 403;   # 缺少签名参数
    }
    if ($secure_link = "0") {
        return 410;   # 签名错误或已过期
    }
}

原理说明:secure_link_md5 定义签名字符串拼接规则(密钥 + 路径 + 过期时间),Nginx 用同样的规则对当前请求计算 MD5 并与 URL 里的 md5 参数比对,再检查 expires 时间戳是否过期。结果通过 $secure_link 变量体现:空表示缺少参数,0 表示校验失败或过期,其他值表示通过。生成 URL 的脚本要和服务端密钥、拼接规则严格一致,这个可以直接在 PHP 等后端语言里实现,配合登录用户动态发放下载链接。

要注意 secure_link 并不适合全站图片:带签名的 URL 会破坏 CDN 缓存和搜索引擎收录(每个签名 URL 都不一样,爬虫看到的是"新页面")。正确用法是只对敏感资源目录(付费内容、私有备份、临时分享文件)启用,公开图片继续用 Referer 方案。

八、配置生效与测试

改完配置先检查语法再平滑重载:

nginx -t
nginx -s reload

然后分别测试正常请求和"盗链"请求:

# 正常请求:带本站 Referer,应该返回 200
curl -I -e "https://www.zz1984.com/post.html" https://www.zz1984.com/usr/uploads/1.jpg

# 模拟盗链:带别的站 Referer,应该返回 403
curl -I -e "https://evil-site.com/page.html" https://www.zz1984.com/usr/uploads/1.jpg

# 空 Referer:curl 不加 -e 参数即可

如果带本站 Referer 也返回 403,检查 server_names 是否匹配、通配符是否写对,或者服务器前面还有一层代理/Varnish 剥掉了 Referer。测试通过后再观察几天 CDN 回源统计和源站访问日志,确认没有误伤正常用户。

总结

Nginx 防盗链配置简单、见效快,是个人站长保护带宽和原创内容的必备技能。核心就一段 location + valid_referers + if 判断,五分钟就能上线;进阶玩法包括正则匹配、警告图替换、目录级保护、CDN 协同,以及面向敏感资源的 secure_link 时效签名。记住几个关键点:搜索引擎域名记得加白名单,否则影响图片收录;接了 CDN 要确认回源 Referer 的透传行为,避免图片全裂;secure_link 只用于敏感资源,别影响公开内容的缓存和收录。配置上线后多观察几天日志,确认无误伤,就能安心把带宽留给真正的访客了。

Last modification:August 11th, 2026 at 08:37 am

Leave a Comment