Nginx 防盗链与静态资源缓存实战:referer 校验、expires 缓存与防滥用排错


<!DOCTYPE html>

Nginx 防盗链与静态资源缓存实战:referer 校验、expires 缓存与防滥用排错

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 抓取器的白名单,这样既不伤正常用户,又能把带宽和服务器资源省下来。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Linux Shell 脚本自动化运维实战:备份清理、存活监控与排错调试

MySQL 主从复制实战:binlog、GTID 与故障切换排错全指南

评 论
更换验证码