⭐ 推荐:社区规则条款 V1.0

小皮面板怎么设置防盗链防止图片被外站盗用?

chinaz
chinaz 初级会员萌新站长
发布于 2026-10-27 01:28 ·2 浏览 ·4 回复
内容摘要

小皮面板无内置防盗链开关,需在 Nginx 站点配置中用 valid_referers 校验 Referer、或在 Apache 的 .htaccess 中用 RewriteCond 拦截,将外站图片请求返回 403 或占位图,同时放行空

小皮面板(phpStudy 面板)没有一键「防盗链」开关,防图片被盗用的正确做法是在站点的 Nginx 配置文件里用 valid_referers 校验 Referer,或是在 Apache 的 .htaccess 里加 RewriteCond 条件,把外站引用的图片请求直接返回 403 或替换成占位图。以下给出可直接粘贴的配置和验证命令。

防盗链的原理是什么

结论:防盗链的本质是校验 HTTP 请求头里的 Referer 字段,判断图片是被哪个域名引用的,不是自己站点就拒绝。

Referer(引用来源)是浏览器在请求图片、脚本等子资源时自动带上的上一页地址。用户在自己站点页面里看图,Referer 是本站域名;被别站 <img src> 直接引用时,Referer 就是对方域名。服务器端匹配白名单,不匹配的请求返回 403 或换成占位图,对方页面上就只剩一个破图或占位提示,带宽和流量也就不再被白嫖。

这个方案有两个固有短板,先说清:一是 Referer 是客户端自愿上报的,用图片代理、伪造 Referer 的采集程序能绕过;二是部分客户端(旧版 App、HTTPS 跳 HTTP、隐私模式)会不带 Referer,处理不当会误伤正常用户。所以配置里必须放行空 Referer。

Nginx 环境怎么配置防盗链

结论:打开小皮面板的站点配置文件,在 server { } 内加一个针对图片后缀的 location 块,用 valid_referers 加 $invalid_referer 判断即可。

操作路径:小皮面板 → 网站 → 找到目标站点 → 管理 → 修改 → 配置文件(即 vhosts.conf 里的对应 server 段),改完点保存,面板会自动执行 nginx -t 校验并 reload;如果没自动重载,到软件管理里对 Nginx 点「重启」。

location ~* \.(jpg|jpeg|png|gif|webp|bmp|svg)$ {
    valid_referers none blocked server_names *.example.com example.com;
    if ($invalid_referer) {
        return 403;
        # 更友好的做法:返回占位图
        # rewrite ^/ /static/nohotlink.png break;
    }
    expires 30d;
    add_header Vary Referer;
}

参数含义:none 放行不带 Referer 的请求,blocked 放行被防火墙或代理抹掉协议头的 Referer,server_names 自动匹配本站 server_name 里配置的所有域名。把 example.com 换成你自己的域名,同时把需要放行的搜索引擎图片域名、CDN 回源域名按 *.baidu.com、*.google.com 这种写法追加进去。

add_header Vary Referer; 这行别省。它告诉 CDN 和浏览器「同一个 URL 在不同 Referer 下内容可能不同」,避免 CDN 把一次 403 缓存下来,导致本站用户也打不开图。

Apache 环境怎么配置

结论:Apache 下不需要改主配置,在网站根目录放一个 .htaccess,用 RewriteCond 匹配 Referer 再用 RewriteRule 拦截即可。

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?baidu\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|bmp|svg)$ - [F,NC]

[F] 直接返回 403;想要换成占位图,把最后一行改成 RewriteRule \.(jpg|jpeg|png|gif|webp)$ /static/nohotlink.png [R,L]。[NC] 表示域名匹配不区分大小写,!^$ 那一行就是放行空 Referer 的条件,删掉它会误伤直接输入图片链接访问的用户。前提是 Apache 已开启 mod_rewrite 且站点配置里 AllowOverride 允许 .htaccess 生效,小皮面板默认的 Apache 站点一般已满足。

配置完怎么验证生效

结论:用 curl -I -e 模拟不同 Referer 请求同一张图片,看返回码是 200 还是 403,比开浏览器刷新更准。


curl -I -e "https://www.example.com/post/1.html" https://www.example.com/uploads/a.jpg

# 2. 模拟外站盗用,应返回 403
curl -I -e "https://evil-site.com/" https://www.example.com/uploads/a.jpg

# 3. 不带 Referer,应返回 200(因为你写了 none)
curl -I https://www.example.com/uploads/a.jpg

三条结果都符合预期,说明规则已生效。如果第二条仍是 200,按顺序排查三点:规则是否加在了正确的 server 段;Nginx 是否真的 reload 成功(面板里看配置校验结果);浏览器/CDN 缓存是否还没过期,加个 ?v=2 参数绕开缓存重测。

防盗链最容易踩的几个坑

结论:防盗链配错导致的投诉,九成出在「白名单漏了正常来源」和「规则范围开太大」这两件事上。

  • 漏掉空 Referer:不放行 none,用户在微信里打开、在地址栏直接输入图片地址、从 HTTPS 页面跳到 HTTP 图片时都会拿到 403,看起来像站点挂了。
  • 漏掉 CDN 回源域名:站点挂了 CDN 后,回源请求的 Referer 可能是 CDN 自己的域名,不放行会导致全站图片 403。
  • 规则写得过宽:直接对整站 location / 做 Referer 校验,会把 CSS、JS、字体文件一起拦掉,页面样式直接崩。只针对图片后缀或 /uploads/ 目录生效。
  • 忘了搜索引擎图片收录:百度、Google 图片搜索的 Referer 域名要放行,否则站点的图片永远进不了图片搜索流量。
  • 防盗链挡不住专业采集:伪造 Referer 或用图片代理的程序可以绕过。这类情况要上更硬的方案:给图片地址加带时间戳的签名(Nginx 的 secure_link 模块),过期即失效;或者把图片挪到对象存储,用私有读 + 临时签名 URL。

配完规则后建议先在自己站点抽样打开 10 张图片确认正常,再去站长类社区发帖请人帮忙从外站引用测试。像 GJ 站长论坛这类面向站长和运维的社区,问答区常有同类配置的排错讨论,把自己的 location 段和 curl -I 结果一起贴出来,定位会快很多;发帖前记得先上传头像,否则账号是发不了帖的。

总结一句:小皮面板不提供防盗链开关,Nginx 用 valid_referers 加 $invalid_referer 返回 403,Apache 用 RewriteCond 加 [F],两者都必须放行空 Referer 并追加 CDN、搜索引擎白名单,改完用 curl -I -e 三条命令验证,再靠签名 URL 或对象存储兜住会伪造 Referer 的那批流量。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2600.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 4

juming
juming 作者初级会员 1楼 2026-10-27 01:41

配置思路没问题,但有两处最容易翻车的地方值得补一下:Nginx 那段 if 不用怕“if is evil”,真正的坑在 Apache 侧的 AllowOverride,以及 CDN 把 403 缓存下来误伤本站用户。

先说 Nginx。valid_referers ... ; if ($invalid_referer) { return 403; } 是官方文档认可的写法,因为 location 内只做 return,不涉及 rewrite 上下文,性能上没有额外开销。建议把 server_names 换成显式域名,比如 valid_referers none blocked example.com www.example.com *.example.com。泛解析或多域名共用 server_name 时,server_names 会把你不希望放行的域名也一起放行。放行名单里记得加上 *.baidu.com、*.google.com、*.sogou.com 这类搜索引擎图片抓取域名,否则图片收录会掉。

Apache 那半段补完:.htaccess 里写 RewriteCond %{HTTP_REFERER} !^$、RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com/ [NC],再配 RewriteRule \.(jpg|jpeg|png|gif|webp)$ - [F,NC]。小皮面板下最常见的“配了完全没反应”是 httpd.conf 或 vhost 里对应 <Directory> 段的 AllowOverride 不是 All,.htaccess 直接失效;其次 mod_rewrite 没开。这两项都要在面板里先确认。

最后提醒误伤面:微信/QQ 内置浏览器、部分安卓 App 请求图片时会带奇怪的 Referer,配置前先开日志看几天真实 Referer 分布再收紧白名单。验证可以直接 curl -e "https://other.com" -I https://你的域名/test.jpg 看返回码,改完务必清一遍 CDN 缓存。很多 CDN 不认 Vary: Referer,一旦把 403 缓存进去,本站用户会集体看到破图。图片量大又上了对象存储的话,其实直接用 OSS/COS 自带的 Referer 黑白名单更省事,不必在源站扛。

lqyyzz
lqyyzz 观望站长 #3216 2楼 2026-10-27 01:49
juming:配置思路没问题,但有两处最容易翻车的地方值得补一下:Nginx 那段 `if` 不用怕“if is evil”,真正的坑在 Apache 侧的 `AllowOv…

补得很到位,尤其 AllowOverride 那条——小皮面板下 .htaccess 配了没反应,九成是它。我再补一个更隐蔽、也更常被人忽略的地方:add_header Vary Referer; 在 403 状态下根本不会下发。

Nginx 的 add_header 和 expires 默认只对 200/201/204/206/301/302/303/304/307/308 这些状态码生效,命中防盗链拦截返回 403 时,这两个响应头都不会被加上。也就是说,CDN 拿到的 403 是一个没有任何 Vary 标记的响应,照样可能被缓存。写法要改成 add_header Vary Referer always;(1.7.5 以后支持 always 参数),或者干脆把防盗链策略放到 CDN 侧做。

如果不想返回 403、想给占位图,error_page 403 /static/nohotlink.png; 比 rewrite 更干净:返回的是 200,缓存头正常下发,占位图又在独立的 /static/ location 里,不会被 valid_referers 再过一遍、也不会自循环。顺便记一下,微信内置浏览器的 Referer 是 servicewechat.com 系,QQ 是 *.qq.com,这两个不放行的话移动端分享出去的图会全变红叉。改之前建议先用 awk -F'"' '{print $4}' access.log | awk -F'/' '{print $3}' | sort | uniq -c | sort -rn | head -20 把真实 Referer 分布拉出来看两天,再按数据收紧白名单。

对象存储那条我完全同意,补一句:OSS/COS 的 Referer 白名单里务必勾上“允许空 Referer”,否则 App 端和直接访问的用户会集体 403,这个比源站配错还常见。

满月居于空
满月居于空 萌新站长 #3219 3楼 2026-10-27 01:55
lqyyzz:补得很到位,尤其 `AllowOverride` 那条——小皮面板下 `.htaccess` 配了没反应,九成是它。我再补一个更隐蔽、也更常被人忽略的地方:`a…

always 这个点确实是大部分人漏掉的,但比它更隐蔽的是 add_header 的继承规则——你在那个 location 里只要写了一条 add_header,server 层的所有 add_header 就全部不再继承。

也就是说,为了防盗链加一行 add_header Vary Referer always;,会顺手把这个 location 里原有的 HSTS、X-Content-Type-Options、X-Frame-Options 这些安全头一起丢掉,静态图片虽然不渲染 HTML,但安全扫描和部分合规检查会直接报出来。要么在这个 location 里把父级的安全头原样补全,要么更省事:用 map $http_referer $vary_flag 之类的变量在 server 层统一 add_header,location 里只做判断不放头。

error_page 那条我建议再明确一下状态码:写成 error_page 403 =200 /static/nohotlink.png;,等号和状态码都写全,避免不同 Nginx 版本对内部跳转后的状态码处理不一致,搜索引擎那边也好判断这是占位而非真实图片。占位图的 location 用 location = /static/nohotlink.png { internal; },加上 internal 之后外部直接访问不了占位图本身,error_page 的内部跳转仍然生效,比放在普通静态目录里干净。

awk 那行提醒一句:$4 是按 combined 格式取的,如果站点的 log_format 改过(加过 $request_time、$upstream_addr 之类),字段位置就变了,跑之前先 head -1 access.log 确认一下分隔情况,不然统计出来全是空的。白名单收紧后建议留一周观察期,先记 log 不拦,确认没有误伤的 Referer 再切到 return 403。

对象存储那条完全同意,另外 CDN 侧和源站别同时开严格防盗链,两边白名单不一致时会出现「源站放行、CDN 拦掉」的诡异现象,排查起来很费时间,建议只在一侧做严格校验。

lqyyzz
lqyyzz 观望站长 #3220 4楼 2026-10-27 02:02
满月居于空:`always` 这个点确实是大部分人漏掉的,但比它更隐蔽的是 `add_header` 的继承规则——你在那个 `location` 里只要写了一条 `add…

add_header 的继承规则确实比 always 更隐蔽。我建议干脆反过来做:那个 location 里一条 add_header 都不写,把防盗链判断整体挪到 map,安全头只在 server 层下发一次,继承问题就不存在了:

map $http_referer $bad_ref {
    default 1;
    ""      0;
    "~*^https?://(www\.)?example\.com/" 0;
    "~*^https?://[^/]*\.(baidu|google|sogou|so)\.com/" 0;
}

location 里只留 if ($bad_ref) { return 403; } 和 expires,父级的 HSTS、X-Frame-Options 全部原样生效。较新的 Nginx 有 add_header_inherit 可以显式控制继承,但线上跑的大多是老版本,不能指望它。这条路还有一个附带好处:map 里可以写正则,白名单维护起来比 valid_referers 的一长串参数直观。

error_page 403 =200 补一点:占位图那个 location 的 expires 别设太长,1h 到 6h 就够。设成 30d 的话,以后想换占位图,CDN 上那批缓存得过一个月才更新;另外 =200 会把 403 从状态码统计里抹掉,日志里最好靠独立的 access_log 或 $bad_ref 变量留个痕,不然事后想统计被拦了多少请求会查不到。

awk 取 $4 那点同意,换成不依赖字段位置的写法更省事:grep -oE 'https?://[^/"]+' access.log | awk -F/ '{print $3}' | sort | uniq -c | sort -rn | head -20,log_format 怎么改都不影响。

观察期我一般这么做:先把 return 403 注释掉,只打日志跑两三天,确认白名单没有误伤再上拦截——移动端和各家 App 内置浏览器的 Referer 比想象中杂得多。