<!DOCTYPE html>
Nginx 防盗链与静态资源缓存实战:referer 校验、expires 缓存与防滥用排错
辛辛苦苦做的图片、视频被别的网站"白嫖"(盗链),带宽费用却要自己出;静态资源每次请求都回源,白白浪费服务器资源。这两个问题都可以用 Nginx 一行行配置解决:valid_referers 防盗链 + expires 浏览器缓存 + open_file_cache 文件缓存。本文从原理到实战,给出完整配置和常见排错,让资源既防盗、又提速。
一、防盗链原理:HTTP Referer 是什么
浏览器请求图片时,会在 Referer 请求头里带上"当前页面地址"。盗链站点在它的页面里 <img src="你的图片地址">,请求到达你的服务器时,Referer 就是盗链站的域名。Nginx 的 valid_referers 指令就是校验这个头:Referer 不在白名单里的请求,直接拒绝。
注意 Referer 只是浏览器主动带的一个"声明",可以被伪造,所以防盗链是"防君子不防小人"——它的作用是挡住绝大多数低成本的盗链,让大多数人在浏览器里看不到盗链图片。对付深度伪造下载需要更重的方案(签名 URL、鉴权接口),本文先解决 90% 的问题。
二、图片防盗链:valid_referers 实战
1. 基础配置:只允许本站域名访问图片
编辑站点配置文件(如 /etc/nginx/conf.d/www.example.com.conf):
server {
listen 80;
server_name www.example.com;
# 图片、CSS、JS 等静态资源目录
location ~* \.(gif|jpg|jpeg|png|webp|bmp|ico|svg)$ {
# none:允许 Referer 为空(直接输网址/收藏夹/部分 APP 打开)
# blocked:允许 Referer 被浏览器去掉协议(如 http:// 被吞)
# 后面跟的是本站白名单域名
valid_referers none blocked
www.example.com example.com *.example.com;
if ($invalid_referer) {
return 403;
}
expires 30d;
add_header Cache-Control "public";
}
}
要点解释:
1. valid_referers 后面第一段是"内建值":none(无 Referer)、blocked(Referer 有但被删协议)、server_names 等 2. 白名单支持 *.example.com 通配,也支持 ~* 正则 3. 校验通过 $invalid_referer 为 0(空),未通过为 1 4. if ($invalid_referer) 里只能使用 return/rewrite 等少量指令,不要放复杂逻辑
重新加载配置生效:
nginx -t && nginx -s reload
2. 用占位图代替 403,提示更友好
直接返回 403 页面太生硬,可以重定向到一张提示占位图:
location ~* \.(gif|jpg|jpeg|png|webp)$ {
valid_referers none blocked www.example.com *.example.com;
if ($invalid_referer) {
rewrite ^/ /hotlink-403.jpg break; # 转到本站的提示图
}
root /var/www/html;
expires 30d;
}
这样盗链站点显示的是你精心准备的"图片已防盗链,请访问本站"提示图,既达到警示效果,又不至于空白一片。
3. 允许特定搜索引擎抓图
想让百度、Google 收录图片,把它们的域名加入白名单:
valid_referers none blocked
www.example.com *.example.com
*.baidu.com *.google.com *.googlebot.com *.bing.com;
三、静态资源缓存:expires 与 Cache-Control
防盗链只解决"别人偷",缓存解决"自己人重复请求"——浏览器把 CSS/JS/图片缓存到本地,第二次访问不再回源,服务器压力骤降。
1. 按文件类型设置不同缓存时长
location ~* \.(js|css)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, must-revalidate";
}
location ~* \.(gif|jpg|jpeg|png|webp|ico|svg|woff2?|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
说明:
1. expires 7d 会自动生成 Expires 头 和 Cache-Control: max-age=604800 2. immutable 表示"文件不会变",配合文件名带哈希(如 app.abc123.js)使用最佳 3. 必须配合"文件名带版本号/哈希"使用,否则更新代码后浏览器还在用旧缓存,这就是最常见的"改完代码没生效"的原因
2. HTML 页面不要缓存
动态页面(HTML、PHP)一定不要开浏览器缓存,否则内容更新不生效:
location ~ \.php$ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
四、open_file_cache:服务器端文件缓存
除了浏览器缓存,Nginx 自己也可以缓存"已打开文件"的元数据(文件大小、修改时间、句柄),减少磁盘 stat 调用,高并发访问静态资源时性能提升明显:
http {
# 缓存最多 10000 个文件条目,20s 内不活跃则淘汰,定期检查有效期
open_file_cache max=10000 inactive=20s;
# 缓存有效检查间隔
open_file_cache_valid 60s;
# inactive 期间最少被访问 2 次才进缓存
open_file_cache_min_uses 2;
# 文件访问不到时是否记录错误日志
open_file_cache_errors on;
}
生效后可用 nginx -V 无关,直接看响应头是否有 ETag/Last-Modified 即可,配合 sendfile on; tcp_nopush on; 一起食用效果更佳。
五、整站统一配置:一个更完整的示例
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
root /var/www/html;
# 静态资源:防盗链 + 长缓存
location ~* \.(gif|jpg|jpeg|png|webp|bmp|ico|svg)$ {
valid_referers none blocked
www.example.com example.com *.example.com
*.baidu.com *.google.com *.bing.com;
if ($invalid_referer) {
rewrite ^/ /hotlink-403.jpg break;
}
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# 前端构建产物:哈希文件名,放心 immutable
location /static/ {
alias /var/www/html/static/;
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
}
# 动态请求不缓存
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
add_header Cache-Control "no-store, no-cache";
}
}
六、常见排错:配置不生效怎么办
1. 验证防盗链是否生效
用 curl 模拟不同 Referer 请求图片:
# 带本站 Referer,应返回 200 curl -sI -H "Referer: https://www.example.com/" https://www.example.com/img/logo.png | head -5 # 带盗链 Referer,应返回 403 或跳转提示图 curl -sI -H "Referer: https://www.bad-site.com/" https://www.example.com/img/logo.png | head -5
如果两种请求都返回 200,说明 valid_referers 没匹配到请求路径——检查 location 正则是否覆盖了图片路径,比如图片在 /img/ 下但你的正则只匹配了 /static/。
2. 排查"配置改了没生效"
1. nginx -t 检查语法,报错会直接告诉你在哪行 2. nginx -s reload 后,用 curl -sI 加 --no-cache 强制回源测试(别被浏览器缓存骗了) 3. 检查浏览器缓存:改代码后 304 还是 200?304 说明在用缓存,换文件名带哈希 4. 看访问日志:error.log 里是否有 invalid referer 相关记录(可临时开 debug 级别)
3. 防盗链误伤正常用户怎么办
最典型的是"图片在自己网站显示正常,但发到微信/QQ/钉钉分享后打不开"——因为这些 IM 的抓取器不带 Referer 或不带白名单域名。处理方式:
# 方案1:允许 none(无 Referer),绝大多数抓取器直接访问无 Referer,能正常抓图 valid_referers none blocked ...; # 方案2:把常用抓取器域名加入白名单 *.wx.qq.com *.qpic.cn *.qq.com *.mp.weixin.qq.com *.dingtalk.com *.feishu.cn;
另外注意:none 也会放行"直接输入图片地址"的用户,盗链者只要在代码里去掉 Referer 就能绕过——这就是前面说的"防君子不防小人",如果真在意,要上签名 URL 方案(Nginx Secure Link 模块):
# 开启 secure_link(需编译时带 --with-http_secure_link_module)
location /files/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri secret_key";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
生成签名 URL 的命令:
# 过期时间戳 + 路径 + 密钥 算出 md5
expires=$(date -d "2027-01-01" +%s)
secret=$(echo -n "${expires}/files/backup.zip secret_key" | md5sum | awk '{print $1}')
echo "https://www.example.com/files/backup.zip?md5=${secret}&expires=${expires}"
没有正确签名的请求一律 403,Referer 伪造也没用,这才是真正防下载接口滥用的方案。
4. 缓存排错:为什么改了文件还是旧内容
1. 浏览器缓存:文件名没带版本号,expires 又设了 30d -> 升级代码必须改文件名 2. CDN 缓存:CDN 节点没刷新,在 CDN 控制台手动刷新缓存 3. 服务器端 open_file_cache 缓存了旧文件元数据 -> reload 不一定清掉,等待 inactive 超时或直接重启 nginx
结语
一套组合拳下来:valid_referers 挡住 90% 的盗链,expires + immutable 让浏览器缓存静态资源,open_file_cache 降低服务器端 IO,静态资源不再白嫖带宽、不再反复回源。记住两个原则:文件名带哈希才能放心长缓存,防盗链要留搜索引擎和 IM 抓取器的白名单,这样既不伤正常用户,又能把带宽和服务器资源省下来。



