ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Nginx防盗链配置实战:从Referer校验到CDN与签名鉴权

2026/9/11 11:08:01 拓冰建站 浏览量
Nginx防盗链配置实战:从Referer校验到CDN与签名鉴权 1. 先搞清楚一件事盗链到底偷走了什么做站点的人迟早会遇到这么一天 — 服务器带宽监控曲线突然拉满明明自己的访问量没涨账单却先爆了。一查访问日志满屏的图片、CSS、附件请求Referer 清一色是别人的域名。这就是典型的资源被盗链别人家的网页里直接写了你站点的图片地址或下载链接访客在对方页面里浏览时浏览器会向你的服务器发起资源请求流量和带宽成本全部由你承担。防盗链的本质很简单Nginx 在返回资源前先检查请求头里的Referer 字段判断“是谁在请求我”。如果来源域名落在白名单之外就拒绝返回资源直接丢一个 403 或者一张替代图。这个手段拦截不了居心叵测的技术人员但能挡住 95% 的“顺手牵羊”对图片站、下载站、视频外链这类场景尤其管用。这篇东西不是照着官方文档念参数而是按我实际配置过的场景来拆从 Referer 机制的原理讲起再给一套可以直接抄的配置模板然后聊几个真实环境里绕不开的坑——比如为什么有的人配了防盗链反而把自己网站图片搞挂了为什么 CDN 开了之后防盗链突然失效。适合刚入门 Nginx 的运维也适合被盗链问题折磨过的后端和前端同学。2. 防盗链的判断依据Referer 头里到底藏了什么2.1 一个典型的盗链请求链路先看一次正常浏览流程用户在浏览器地址栏输入www.a.com/page.html页面里有一张img srchttps://www.a.com/images/1.jpg。浏览器请求图片时HTTP 请求头里会带上这样一行Referer: https://www.a.com/page.html这行信息告诉服务器“我是从 a.com 这个页面过来的来取一张图片。” 如果有人在www.b.com的网页里直接引用了你的图片地址那浏览器请求到你的服务器时请求头里的 Referer 就会变成https://www.b.com/page.html。服务器通过对比这个字段和配置的白名单就能识别出来源是否合法。需要特别提醒一下这里存在一个前置条件Referer是浏览器主动携带的正常情况下由浏览器根据下一页面的来源地址自动填充。它不是一种强认证机制更像是“自报家门”。所以防盗链拦截的是“无意识盗用”真正有技术能力伪造 Referer 的人靠这一层拦不住。2.2 Referer 的三种特殊状态我们在配置里经常见到none、blocked、server_names这些关键字它们对应的就是 Referer 的几种不同状态状态含义常见触发场景none请求头里没有 Referer 字段用户直接在浏览器地址栏输入图片地址访问、部分爬虫请求、某些下载工具blocked请求头里有 Referer但被代理或浏览器策略抹掉了实际来源通过 HTTPS 页面请求 HTTP 资源、部分浏览器隐私模式、个别反代配置正常值带有具体的来源 URL从某个网页点击跳转或引用而来none和blocked这两个状态是配置里最容易出问题的点。如果你配置了valid_referers none blocked;那就意味着“允许不带来源和来源被抹掉的请求”。很多静态资源从 HTTPS 页面被引用到 HTTP 资源时浏览器会主动去掉 Referer被判定为none如果不放行就会出现“明明是自己网站图片却裂了”的情况。2.3 为什么不能靠 Referer 做到 100% 防护前面说了Referer 是浏览器“自愿”带的字段。curl 发送请求时可以随意指定也可以用-e参数仿冒一个来源用爬虫框架时同样能伪造甚至有些下载软件会故意不放 Referer。所以 Referer 防盗链只能防君子不防小人。不过这并不代表它没有价值。现实场景中绝大多数盗链都来自简单的img直链引用、论坛帖子里贴图、第三方站点抓取内容这种情况下浏览器会老老实实带上来源页的 RefererNginx 的防盗链配置足以拦截绝大部分。真正需要更高强度防护的资源比如付费下载的安装包、会员专属视频一般不会只用 Referer而是会叠加 URL 签名、时间戳鉴权甚至 Cookie 校验。这个话题后文会展开。3. 基础配置两条指令实现最简防盗链3.1 valid_referers 指令的语法拆解Nginx 的防盗链配置核心是valid_referers和内置变量$invalid_referer的组合。valid_referers定义了“合法来源”的匹配规则Nginx 会依次检查请求的 Referer 是否符合规则匹配成功时把$invalid_referer变量置为空字符串匹配失败时置为1。基本语法如下valid_referers none blocked server_names *.example.com example.* www.example.org/gallery ~\.googlebot\.;none允许没有 Referer 的请求blocked允许 Referer 被隐藏的请求server_names允许请求的 Host 与 server_name 匹配的请求具体的域名或带*通配符的域名允许 Referer 匹配这些域名的请求以~开头的正则表达式允许 Referer 匹配该正则的请求注意*通配符只能用在域名开头或结尾*.example.com可以匹配www.example.com、img.example.com但不能匹配example.com本身所以要同时写example.com *.example.com两个。3.2 一个能直接抄的图片防盗链配置假设我管理的站点主域名是www.example.com同时还有一个图片子域名img.example.com图片目录是/usr/share/nginx/html/images/。下面这套配置可以直接放到 server 块里server { listen 80; server_name www.example.com img.example.com; location ~* \.(gif|jpg|jpeg|png|webp|bmp|ico)$ { valid_referers none blocked example.com *.example.com; if ($invalid_referer) { return 403; } root /usr/share/nginx/html; expires 30d; access_log off; } location / { root /usr/share/nginx/html; index index.html; } }这套配置干了几件事用正则location ~* \.(gif|jpg|jpeg|png|webp|bmp|ico)$匹配常见的图片扩展名~*表示忽略大小写。请求任何这类文件时进入该 location 块。valid_referers允许三种来源无 Referer、被隐藏的 Referer、来自 example.com 或其子域的 Referer。if ($invalid_referer)判断 Referer 是否合法非法则返回 403。合法请求继续走正常的文件返回逻辑并额外配了 30 天浏览器缓存和关闭访问日志减轻服务器压力。3.3 if 指令的争议点与替代写法Nginx 社区对if指令的“滥用”一直有争议官方文档也警告过if在 location 里可能引发不可预期的行为。但防盗链这个场景有一点特殊我们只使用了return没有在if内部做 rewrite、proxy_pass 这类操作。根据 Nginx 官方对 “If is Evil” 一文的解释if块里只包含return指令时是安全的官方也推荐在这种场景下使用。所以上面这份配置可以直接用不会有那些隐蔽的坑。如果你对if仍然不放心还有另一种不依赖if的写法利用error_page把 403 请求导向一个替代图片location ~* \.(gif|jpg|jpeg|png|webp|bmp|ico)$ { valid_referers none blocked example.com *.example.com; if ($invalid_referer) { return 403; } root /usr/share/nginx/html; } error_page 403 /403.png; location /403.png { root /usr/share/nginx/html/images/; }这种方式下盗链者看到的不再是死链接而是一张“禁止外链”的提示图片。体验上比光秃秃的 403 友好一些也更容易向网站管理者传递“请勿盗链”的信息。4. 分场景的防盗链实战配置4.1 场景一多域名白名单与泛域名匹配实际业务里合法来源往往不止一个域名。比如公司官网是example.com还有一个营销落地页promo.example.net以及一个正在备案的临时域名pre.example.org。这时候可以把valid_referers写成这样valid_referers none blocked example.com *.example.com promo.example.net *.example.org;每一行一个域名或通配符Nginx 会从左到右逐个匹配命中任意一条就算合法。这个列表支持用server_names关键字替代部分域名例如当前 server 块监听了多个server_name可以把这些名字统一收纳server { listen 80; server_name www.example.com img.example.com static.example.com; location ~* \.(gif|jpg|jpeg|png|webp)$ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } ... } }server_names的作用是自动把当前 server 块中配置的server_name视为合法来源。这个写法在维护多个子域名时能少写几行但要注意它只能覆盖本 server 块的域名其他域名仍需要手动声明。4.2 场景二文件下载站的防盗链文件下载站和图片站有一个明显区别对none的处理思路不同。图片站一般允许浏览器直接打开图片none放行但下载站往往希望禁止没有来源的请求因为很多盗链工具和采集器不会带 Referer。同时下载站的资源一般较大被批量盗链时带宽消耗非常可观。下载站的防盗链配置通常长这样location ~* \.(zip|rar|7z|exe|apk|ipa)$ { valid_referers blocked example.com *.example.com; if ($invalid_referer) { return 403; } root /data/downloads; limit_rate 800k; }注意到这里我没有写none。这意味着用户如果在地址栏直接输入.zip链接请求里没有 Referer会被拦截返回 403。这是一种取舍牺牲“直接访问链接”的便利性换取对无来源批量抓取的更高拦截力度。如果你希望用户在浏览器直接访问时也能下载就需要加回none这属于业务选择的范畴。limit_rate 800k是我额外加的一道保险。即使防盗链被绕过单连接下载速度也被限制在 800KB/s能够降低带宽被占满的风险。配合 Nginx 的limit_conn_zone还可以限制同一 IP 的连接数防盗效果更好。4.3 场景三Nginx 反向代理环境下的防盗链很多项目的架构不是 Nginx 直接返回静态文件而是用 Nginx 做反向代理转发到后端服务比如 Java 应用、Node 服务。防盗链在这种架构下同样适用但要正确区分两个角色外层 Nginx 负责拦截和校验内层服务负责业务逻辑。location /files/ { valid_referers none blocked example.com *.example.com; if ($invalid_referer) { return 403; } proxy_pass http://backend_storage; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }内层后端服务不需要关心 Referer 是否合法它接收到的请求都已经被外层 Nginx 过滤过了。这样做的好处是即使后端有多个节点比如上传的文件存放在内网存储集群防盗链逻辑只需在最外层入口配置一次所有节点统一生效。有一点容易踩坑多层反代时Referer头默认会被透传但如果你中间夹了一层 CDN 或 WAF有些厂商会改写或删除 Referer。这时候外层拿到的是none或blocked如果配置里没放行会导致合法用户被误杀。配置前最好先确认链路中各层对 Referer 的处理规则用 curl 从最外层模拟一次请求看实际带过来的 Referer 是什么。5. 如何验证防盗链配置真正生效5.1 用 curl 模拟不同来源配置写完了不能盲目上线先在本地模拟几类请求验证行为是否符合预期。curl 可以通过-e参数指定 Referer这是最直接的测试手段# 模拟来自合法域名的请求应当返回 200 curl -I -e https://www.example.com/page.html http://your-server/images/logo.png # 模拟来自非法域名的请求应当返回 403 curl -I -e https://www.attacker.com/index.html http://your-server/images/logo.png # 模拟无 Referer 请求不携带 -e视配置决定返回 200 或 403 curl -I http://your-server/images/logo.png-I表示只获取响应头不下载正文测试图片接口时能省带宽。输出内容主要看两行HTTP/1.1 200 OK或HTTP/1.1 403 Forbidden以及响应头中的Server: nginx。5.2 检查 Nginx 访问日志线上环境不可能每次都用 curl 去测最靠谱的验证方式是看访问日志。假设日志格式里包含$http_referer可以按状态码和来源搜索# 查找返回 403 的图片请求及对应来源 grep 403 /var/log/nginx/access.log | grep -E \.(gif|jpg|png) | tail -20 # 按 Referer 域名统计图片请求数量 awk {print $11} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head日志里能直接看到被拦截的请求来自哪个 Referer、请求了哪个 URI。如果我配置防盗链后发现某些“合法来源”也在 403 列表里多半是valid_referers没有匹配全比如漏了子域名或二级域名。5.3 浏览器开发者工具实测浏览器测试更贴近真实用户体验。在站点页面里打开开发者工具切到 Network 面板刷新页面后找到一张图片请求点击查看请求头合法页面内的图片请求Referer显示为当前页面地址响应状态是 200。从盗链页面发起请求时响应头会直接显示 403图片裂开。直接新开标签页访问图片地址Referer为空响应状态取决于是否配置了none。这一步主要确认用户体验没有受损。很多团队配置防盗链后只验证了盗链场景忘了验证自家页面结果线上图片全裂这种事故我见过不止一次。6. 常见问题与避坑指南6.1 高频问题速查表现象可能原因解决思路自己网站图片全部裂掉页面从 HTTPS 请求 HTTP 图片浏览器移除了 Referer或站点域名未进入白名单确认none和blocked是否放行补充站点域名白名单把资源切到 HTTPS配置了防盗链但盗链仍生效盗链者来自无 Referer 场景如直接 HTTP 客户端或 Referer 被伪造收紧valid_referers考虑叠加 URL 签名或时间戳鉴权CDN 场景下防盗链失效CDN 回源时未透传 Referer或 CDN 节点缓存了资源检查源站 Nginx 日志中的 Referer 是否为空CDN 控制台开启回源 Referer 透传下载链接在微信/QQ 内打不开部分 App 内置浏览器会移除 Referer白名单未覆盖 App 的 UA 来源按业务决定是否放行none或针对特定 UA 单独放行403 图片提示破图不美观直接返回 403 导致浏览器展示破图图标配置error_page 403返回一张提示替代图或 rewrite 到占位图后台管理系统上传图片失败管理后台的请求带的是后台域名不在白名单在valid_referers中添加后台域名或针对管理路径单独关闭防盗链搜索引擎爬虫图片被拦爬虫的 Referer 可能为空或与白名单不匹配用正则放行常见爬虫来源如~\.googlebot\.、~\.bing\.或放行none配置 reload 后仍不生效配置可能写错位置或未真正 reload用nginx -t校验语法确认后/usr/sbin/nginx -s reload6.2 一个典型的“自己打自己”案例有一次我给某客户配完防盗链测试自己网站时发现所有图片正常但过了两天客户反馈后台编辑器里的图片全部显示不了。查日志发现请求后台图片的 Referer 是https://admin.example.com/edit/post/12而这个域名不在白名单里。核对后才发现客户的后台部署在独立的 admin 子域图片引用的是主站域名。这是防盗链配置里非常容易踩的坑valid_referers匹配的是来源页面的域名不是资源所在域名。后台页面在 admin 子域它的 Referer 自然是 admin.example.com如果白名单只写了主站域名请求必然被拦。处理方式有两种把 admin 子域加进白名单或者针对/admin/路径单独关闭防盗链。大多数内容管理系统的后台都有这类需求配置前先盘点一遍哪些域名会合法地引用你的资源。6.3 防盗链与缓存、CDN 的联动问题另一个高频坑出现在 CDN 和缓存层。假设你的资源已经上了 CDNCDN 节点缓存了图片后用户命中 CDN 缓存时请求根本不会回到源站 Nginx防盗链判断自然不生效。这种场景下防盗链应该在 CDN 层做配置比如在 CDN 控制台设置 Referer 黑白名单。如果你同时启用了源站防盗链需要确认 CDN 回源时是否带着原始请求的 Referer。很多 CDN 默认回源时使用自己的回源 HostReferer 可能被改写或置空这时源站如果拒绝了none或者blockedCDN 会回源失败表现为全站资源加载异常。我所在团队踩过一次上线 CDN 后突然接到大量告警源站 403 成倍增长定位发现 CDN 回源时 Referer 全部为空源站配置放行了none才恢复正常链路。这个排查思路希望对你有参考价值每当引入新的中间层CDN、WAF、反代后都要回到源站日志里看真实到达的请求头而不是凭大脑里的经验做判断。6.4 更严格的防盗链方案URL 签名与时间戳如果业务对资源保护要求更高比如禁止一切非授权访问、甚至不允许直接复制链接分享Referer 校验就无能为力了。可以升级到 Nginx 的secure_link模块它基于 HMAC 对 URL 生成签名并附带过期时间。只有带正确签名的链接才能在有效期内访问资源链接过期或签名被篡改时直接返回 403。这种方式在付费下载站、在线教育视频点播、私密文件分享中很常用。实现思路也不复杂发放链接的一方按约定密钥生成/files/文件名?md5签名过期时间戳形式的 URLNginx 侧用secure_link_secret或secure_link_md5指令校验。签名算法通常是一段简短的脚本# 约定密钥 secret_key文件路径 /files/release.zip过期时间 1800 秒 expires$(date -d 1800 seconds %s) token$(echo -n secret_key/files/release.zip${expires} | openssl md5 -hex | awk {print $1}) echo https://your-server/files/release.zip?sign${token}expires${expires}对应的 Nginx 校验配置location /files/ { secure_link $arg_sign,$arg_expires; secure_link_md5 secret_key$uri$arg_expires; if ($secure_link ) { return 403; } if ($secure_link 0) { return 410; } }这套方案把资源访问从“依赖来源”升级为“依赖凭证”能防住大部分抓取和盗链。缺点是 URL 会变长、不便收藏传播也需要业务方配合生成签名所以一般只用于高价值资源的保护。6.5 日志级别与生产环境的后续建议上线防盗链后建议把访问日志里的 Referer 字段保留一段时间。你可能会想看被拦截的来源有哪些拦截量有多大是来自真实盗链站点还是搜索引擎爬虫亦或是自家某些页面配置遗漏。有了这些数据才能持续优化白名单。默认 Nginx 的 combined 日志格式里$http_referer就在其中不需要额外调整如果你自定义过日志格式确认是否包含了该字段。最后提一句Nginx 的防盗链只是 Web 安全中的一个很小的环节它保护的是资源访问层面的“体面”问题解决不了深入的应用漏洞或业务逻辑漏洞。配置完成后我通常会顺手检查一下该 server 块里是否还有其他静态用的 location 也暴露了资源目录如果有同样按需加上校验避免绕过一个 location 就轻松取到资源。边边角角的地方往往才是真正出问题的地方——这句话放在防盗链这里再贴切不过。