1. 项目概述为什么你的网站流量总被“偷走”做网站的朋友尤其是内容站、图片站或者资源站的站长可能都遇到过一种“哑巴亏”服务器带宽跑得飞快账单蹭蹭往上涨但自己网站的访问量却没见多大增长。一查日志发现大量请求来自其他域名自己的图片、视频、文件被别的网站直接拿去用了对方没付一分钱带宽费却让你承担了全部的服务器开销。这就是典型的“盗链”行为。今天要聊的就是如何利用 Nginx 这把“瑞士军刀”给你的网站资源加上一道坚固的锁把那些不请自来的“流量小偷”挡在门外。防盗链顾名思义就是防止别人非法链接你的资源。它的核心原理并不复杂就是检查每个请求的Referer或Origin头信息判断这个请求是否来自你认可的“白名单”站点。如果是放行如果不是就拒绝访问或返回一个替代内容比如一张警告图片。Nginx 作为高性能的 Web 服务器和反向代理内置了强大的ngx_http_referer_module模块让我们可以非常方便地实现这一功能。无论你是个人博客站长还是企业级应用运维掌握这套配置都能有效保护你的数字资产和服务器资源避免不必要的财务损失。2. 防盗链的核心原理与方案选型在动手配置之前我们必须先搞清楚防盗链到底在防什么以及 Nginx 提供了哪些武器。这能帮助我们在后续配置中做出更精准的判断。2.1 盗链是如何发生的想象一下你有一个非常精美的图片地址是https://your-site.com/image.jpg。另一个站长看到了他不想把图片下载下来再上传到自己的服务器那样会占用他的存储空间于是直接在他的网页里写上了img srchttps://your-site.com/image.jpg。当用户访问他的网页时浏览器就会直接向你的服务器请求这张图片。对你而言这个请求的Referer头即来源页地址是对方的域名如https://thief-site.com而不是你自己的域名。你的服务器在不知情的情况下消耗了带宽和计算资源为别人的网站提供了服务。这就是一次成功的盗链。2.2 Nginx 的两种主流防盗链机制Nginx 主要通过检查 HTTP 请求头来实现防盗链最常用的是以下两种基于Referer头的防盗链这是最经典、最常用的方法。Referer头注意拼写HTTP 标准里就是错的包含了当前请求是从哪个页面链接过来的。通过配置valid_referers指令我们可以定义一个或多个被允许的Referer模式。如果请求的Referer不在白名单内或者根本没有Referer头比如直接从浏览器地址栏输入URL或者某些客户端会刻意去掉此头Nginx 就可以执行拒绝操作。优点配置简单直观对绝大多数普通盗链行为有效。缺点Referer头可以被客户端伪造或禁用安全性不是绝对的。但对于提升盗链成本、防止无心之失和常规爬虫已经足够。基于secure_link模块的防盗链这是一种更高级、更安全的方案。它的原理是生成一个有时效性、带签名的访问链接。服务器在收到请求后会验证链接中的签名和时间戳是否有效。只有验证通过的请求才能获取资源。优点安全性极高无法伪造可以精确控制每个链接的过期时间。适用于付费内容、一次性下载链接等场景。缺点配置相对复杂需要后端程序配合生成签名链接改变了资源的访问方式。对于绝大多数内容展示型网站博客、图库、资讯站基于Referer的防盗链是性价比最高的选择。本文将重点深入讲解这种方式的配置、优化和避坑指南。secure_link方案我们会在最后进行简要对比和场景分析。2.3 方案选型背后的考量为什么首选 Referer选择基于Referer的方案不仅仅是因为它简单。从实际运维角度出发有以下几个关键考量覆盖场景90%以上的盗链来自于其他网站的直接引用这些请求都会携带Referer头此方案能直接拦截。维护成本配置写在 Nginx 中清晰易懂排查问题方便。无需改动现有的内容发布流程。性能开销valid_referers指令检查是 Nginx 内部的高效字符串匹配性能损耗几乎可以忽略不计不会影响正常访问速度。渐进增强可以先实施基础的Referer防盗链如果后续发现有针对性的、伪造Referer的高级盗链再考虑升级到secure_link或其他方案如结合用户认证。注意Referer防盗链并非银弹。它无法防止通过下载工具直接抓取、或者恶意攻击者伪造请求头的情况。它的主要目标是“增加盗链的难度和成本”将那些不费吹灰之力的资源窃取行为挡在外面这已经能解决绝大部分实际问题了。3. 核心配置解析与实操要点理解了原理我们进入实战环节。Nginx 的ngx_http_referer_module模块默认是编译在内的我们直接使用即可。核心指令只有一个valid_referers。但围绕它展开的配置却有很多细节需要注意。3.1valid_referers指令详解这个指令用于定义合法的来源Referer。它的语法是valid_referers none | blocked | server_names | string ...;可以同时指定多个参数参数间用空格隔开。Nginx 会依次检查只要满足其中一个条件即视为合法。none允许缺失Referer头的请求访问。这是一个非常重要的选项。因为有些合法的访问场景是没有Referer的比如用户在浏览器地址栏直接输入图片URL。从本地 HTML 文件打开file://协议。某些浏览器隐私模式或安全设置下。一些邮件客户端、办公软件内嵌浏览器。 如果你不加上none这些正常用户的直接访问也会被拦截导致图片无法显示。blocked允许Referer头存在但其值被防火墙或代理服务器修改、删除即为空字符串或以http://或https://开头的请求。这个参数主要用于处理一些网络环境对Referer的干扰。server_names允许Referer头中的主机名与当前server_name指令列出的任何一个名称相匹配的请求。这是最常用的白名单方式用于允许自己网站内部的引用。string可以是一个确切的域名如trusted.com一个带通配符的域名如*.trusted.com或者一个正则表达式以~开头。用于添加特定的合作伙伴或可信站点到白名单。3.2 基础配置模板与位置选择防盗链配置通常放在location块中针对特定的资源类型如图片、视频、下载文件进行保护。一个最基础、最安全的配置模板如下location ~* \.(jpg|jpeg|png|gif|ico|css|js|mp4|pdf)$ { valid_referers none blocked server_names *.yourdomain.com yourdomain.com; if ($invalid_referer) { return 403; # 或者重写到一个警告图片 # rewrite ^ /path/to/warning.jpg last; } }配置解读location ~* \.(jpg|jpeg|png|gif|ico|css|js|mp4|pdf)$这是一个不区分大小写的正则匹配对所有以列出的后缀结尾的请求图片、样式、脚本、视频、PDF应用防盗链规则。你可以根据自己网站的资源类型调整这个列表。valid_referers none blocked server_names *.yourdomain.com yourdomain.com;定义合法来源。none允许无Referer的直接访问。blocked允许被修改过的Referer。server_names允许来自本服务器配置中server_name所列域名的请求。*.yourdomain.com yourdomain.com显式添加你自己的主域名和所有子域名到白名单。这是双保险确保所有自家站点的引用都畅通无阻。if ($invalid_referer) { ... }内置变量$invalid_referer在Referer非法时为1合法时为0。如果非法我们执行return 403;返回一个“禁止访问”的 HTTP 状态码。这是最直接的处理方式。位置选择的心得不要放在server块顶层这会对所有请求生效包括 HTML 页面本身容易误伤。推荐针对资源类型配置如上述例子精准控制。如果你的静态资源都放在/static/或/uploads/目录下也可以用location /static/或location /uploads/来匹配更清晰。注意配置的优先级Nginx 的location有优先级规则精确匹配 前缀匹配^~ 正则~/~* 通用前缀/。确保你的防盗链location块能正确匹配到目标请求。3.3 进阶使用map指令实现更灵活的白名单管理当你的白名单域名比较多或者规则复杂时把一大堆域名写在valid_referers后面会显得很臃肿。这时可以使用map指令来管理让配置更清晰也便于动态更新结合include。# 在 http 块中定义一个 map将合法的 Referer 映射为 1 map $http_referer $valid_referer_map { default 0; # 默认都是非法的 ~*\.?yourdomain\.com 1; # 允许自己所有域名 ~*trusted-partner\.com 1; # 允许合作伙伴A ~*another-site\.org 1; # 允许合作伙伴B # 可以继续添加更多... 1; # 允许空 Referer (对应 none)注意这里空字符串的写法 ~*^https?:// 1; # 一个简单的 blocked 效果匹配以 http:// 或 https:// 开头的被修改的 Referer } server { ... location ~* \.(jpg|png|gif|mp4)$ { # 使用 map 结果进行判断 if ($valid_referer_map 0) { return 403; } } }使用map的优势集中管理所有白名单规则在一个map块里一目了然。逻辑清晰map的匹配顺序是从上到下优先级可控。便于复用这个map可以在http块中定义供多个server或location使用。动态加载可以将map配置单独写在一个文件里用include指令引入。当需要更新白名单时只需修改这个文件然后reloadNginx 即可无需改动核心的业务location配置。实操心得对于中小型站点直接写在location里足够。但如果你的站点需要对接多个广告平台、内容分发网络CDN或有很多合作伙伴强烈建议使用map方式。前期多花10分钟搭建这个结构后期维护效率会提升十倍。4. 完整配置流程与场景化实战现在我们从一个完整的 Nginx 配置片段出发一步步拆解如何为不同类型的网站部署防盗链并处理各种边界情况。4.1 场景一个人博客/图库基础防护假设你的博客域名为blog.example.com所有图片都存放在/wp-content/uploads/目录下。server { listen 80; server_name blog.example.com; root /var/www/blog; # 核心防盗链配置 location ~* \.(jpg|jpeg|png|gif|webp|bmp|ico|svg)$ { # 允许无Referer、被修改的Referer、本服务器名、自己的主域和子域 valid_referers none blocked server_names *.example.com example.com blog.example.com; # 如果Referer非法则返回403禁止并记录日志 if ($invalid_referer) { access_log /var/log/nginx/hotlink.log; # 可选将盗链请求记录到单独日志 return 403; } # 以下是静态文件优化配置与防盗链无关但通常一起设置 expires 30d; # 客户端缓存30天 add_header Cache-Control public, immutable; } # 其他location配置比如PHP-FPM处理 location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { ... # PHP处理配置 } }关键点解析白名单除了server_names我们显式添加了*.example.com和example.com。这是因为如果你的博客有移动端m.example.com或未来有其他子站它们之间的图片引用也需要被允许。日志记录access_log /var/log/nginx/hotlink.log;这行配置在if块内意味着只有盗链请求会被记录到这个特定文件。这对于后期分析盗链来源、评估防盗链效果非常有帮助。记得定期检查这个日志文件。结合缓存防盗链location块里通常也会配置静态资源缓存expires,add_header Cache-Control这是最佳实践能极大减轻服务器压力。4.2 场景二资源下载站精准控制与友好提示对于提供软件、模板、电子书下载的站点直接返回403对误入的用户可能不太友好。我们可以选择返回一张自定义的警告图片或者重定向到一个说明页面。server { server_name download.example.com; root /var/www/download; # 设置一个统一的“盗链警告”图片路径 set $hotlink_image /assets/hotlink-denied.jpg; location ~* \.(zip|rar|7z|tar\.gz|dmg|exe|msi|pdf|epub)$ { valid_referers none blocked server_names *.example.com *.trusted-review-site.com search.yahoo.com; # 允许搜索引擎收录可选见下文讨论 if ($invalid_referer) { # 情况1如果是图片请求直接返回警告图片防止盗链者嵌套图片 if ($request_filename ~* \.(jpg|png|gif)$) { rewrite ^ $hotlink_image last; } # 情况2对于下载文件重定向到警告页面或返回警告图片 # rewrite ^ /hotlink-warning.html redirect; # 302重定向到页面 rewrite ^ $hotlink_image last; # 更常用直接返回警告图片 } # 正常访问时设置附件下载头 if ($valid_referer) { add_header Content-Disposition attachment; filename$arg_filename; # 假设通过参数传递文件名 } } # 警告图片本身应该允许所有人访问否则会循环重定向 location /assets/hotlink-denied.jpg { # 这里不做防盗链检查 expires max; add_header Cache-Control public; } }关键点解析差异化处理通过判断$request_filename请求的文件名可以对图片和其他下载文件做不同的处理逻辑更灵活。友好提示返回一个设计过的警告图片上面可以写“此资源仅供本站用户下载”等比生硬的403页面体验更好也能起到宣传自己网站的作用。避免循环至关重要用于替换的警告图片/assets/hotlink-denied.jpg必须设置一个独立的location并且不能应用防盗链规则。否则当盗链请求被重写到这个图片时又会触发防盗链检查导致无限循环或错误。搜索引擎考虑例子中加入了search.yahoo.com。对于希望被搜索引擎收录文件信息的站点可以考虑将主流搜索引擎的域名加入白名单如images.google.com,www.bing.com这样搜索引擎爬虫才能正常抓取和索引你的文件信息在搜索结果中展示。但这需要权衡因为这也意味着搜索引擎可以展示你的文件直链。4.3 场景三CDN 与反向代理后的配置调整如果你的网站前面有 CDN如 Cloudflare、阿里云CDN或使用了 Nginx 本身做反向代理防盗链配置的位置需要特别注意。原则防盗链检查应该在最外层、最先接触到用户真实请求的地方进行。情况ANginx - 后端应用如果你的架构是用户 - Nginx反向代理 - PHP/Java/Python 应用那么防盗链配置应该放在作为反向代理的 Nginx 上。情况BCDN - 源站 Nginx如果你的架构是用户 - CDN - 源站 Nginx那么最佳实践是在 CDN 层面配置防盗链。各大CDN服务商都提供此功能性能更好配置更简单还能减轻源站压力。如果必须在源站 Nginx 配置你需要识别 CDN 回源请求。CDN 回源时Referer头通常是用户的原始Referer大部分CDN默认会传递。但有些CDN可能会修改或添加特定头。你需要查阅CDN文档确保你的valid_referers白名单包含 CDN 的特定回源头如果有或者确保CDN传递了正确的Referer。CDN 回源时的配置示例以传递原始 Referer 的 CDN 为例# 源站 Nginx 配置 server { listen 80; server_name origin.example.com; location ~* \.(jpg|png|css|js)$ { # 白名单允许无Referer、被修改的Referer、自己的域名、以及CDN节点IP如果CDN用IP回源 valid_referers none blocked server_names *.example.com ~.\.cdn-provider\.com$; # 如果CDN有固定域名可以加在这里 # 一个更保险的做法检查CDN传递的特殊头部如 X-Forwarded-Host # if ($http_x_forwarded_host ! cdn.example.com) { # 示例非通用 # set $valid_referer 0; # } # 将CDN的特殊头判断与原有逻辑结合... if ($invalid_referer) { return 403; } # 静态资源服务配置 expires 1y; add_header Cache-Control public; } }踩坑记录曾经有次配置后发现通过 CDN 访问图片全部 403。排查后发现CDN 配置中开启了“过滤参数”功能这导致回源时 URL 改变但更关键的是某些 CDN 在特定模式下可能不会 100% 传递原始Referer。最后解决方案是在 CDN 控制台将“Referer 防盗链”功能关闭避免重复检查并确保 CDN 的“回源 HOST”设置正确同时在源站 Nginx 的白名单里加入了 CDN 的官方回源域名段。教训在多层架构下做防盗链一定要清楚每一层对 HTTP 头的处理逻辑。5. 高级技巧、问题排查与效果验证配置上了不代表就万事大吉。我们需要验证它是否生效并处理一些棘手的边缘情况。5.1 验证防盗链是否生效本地测试在浏览器中打开你的网站页面图片/资源应该正常显示。新建一个本地 HTML 文件里面用img标签引用你网站的一张图片地址。用浏览器打开这个本地文件图片应该无法加载显示403或你的警告图。使用curl命令模拟盗链请求# 不带 Referer应该被允许因为配置了 none curl -I https://your-site.com/image.jpg # 带一个非法域名的 Referer应该被拒绝 curl -I -H Referer: https://some-thief-site.com https://your-site.com/image.jpg # 带自己域名的 Referer应该被允许 curl -I -H Referer: https://your-site.com/some-page.html https://your-site.com/image.jpg观察返回的 HTTP 状态码403 Forbidden表示拦截成功。线上监控查看错误日志在 Nginx 配置中为防盗链的location块设置单独的error_log或记录到access_log观察是否有大量的403错误。分析访问日志使用awk,grep或日志分析工具如 GoAccess, AWStats分析access.log查看图片等资源的请求来源$http_referer字段。配置生效后来自外部域名的请求应该大幅减少或消失。服务器带宽/流量监控最直观的指标。实施防盗链后服务器的出站带宽特别是非高峰时段应该有可观的下降。5.2 处理特殊场景与常见问题问题1搜索引擎收录的图片/文件无法显示了这是因为搜索引擎爬虫如 Google Images抓取图片时Referer可能是它自己的域名。如果你希望图片能在搜索引擎结果中显示需要将主要搜索引擎的图片搜索域名加入白名单。但这有风险因为用户可以通过搜索引擎结果页直接拿到图片直链。方案A推荐使用robots.txt文件禁止搜索引擎抓取你的静态资源目录。User-agent: *Disallow: /uploads/。这样一劳永逸但牺牲了 SEO。方案B权衡将images.google.com,www.bing.com/images等加入白名单。需要定期维护这个列表。问题2手机APP/桌面应用无法加载我的图片很多原生应用发起网络请求时不会发送Referer头或者发送的Referer不符合你的白名单规则如file://,android-app://。方案如果你的资源需要被特定的、你信任的官方应用调用你有两个选择为应用单独开接口设计一个需要认证API Token的接口来获取资源绕过防盗链。放宽白名单如果应用请求的Referer有固定模式如某个自定义协议头可以尝试用正则匹配将其加入白名单。但更安全的还是方案1。问题3配置了none但直接访问图片URL还是被拒检查你的location匹配是否正确。另外确保valid_referers行里确实包含了none关键字。一个常见的错误是在复杂的if条件逻辑中$invalid_referer变量可能在其他地方被意外地重置了。问题4if指令的陷阱Nginx 的if指令在location上下文中有一些“坑”。它被认为是“邪恶的”因为其行为有时不符合直觉它创建了一个隐式的嵌套位置。在防盗链场景中一个经典的错误是location / { valid_referers ...; if ($invalid_referer) { return 403; } proxy_pass http://backend; # 这个指令可能因为 if 而无法在同一个上下文中正确执行 }最佳实践将防盗链的if判断放在只处理静态文件的location块中避免与proxy_pass,fastcgi_pass等复杂的代理指令直接混用。如果必须在代理场景使用考虑使用map变量或ngx_http_rewrite_module的其它方式。5.3 性能优化与安全加固正则表达式优化valid_referers后面的正则表达式要尽量精确避免过于宽泛的.*匹配以减少性能开销。使用map预编译如前所述使用map指令Nginx 会在启动时编译好映射表运行时查找是 O(1) 或 O(log n) 的复杂度比在location中写一长串valid_referers性能稍好尤其是在白名单很长时。结合limit_req或limit_conn对于恶意盗链或攻击单纯的403可能不够。可以结合ngx_http_limit_req_module对来自非法Referer的请求进行速率限制或者直接使用ngx_http_access_module封禁特定 IP 段。定期审查日志定期检查防盗链日志 (hotlink.log)分析盗链来源。如果发现某个域名大量盗链可以考虑将其IP段加入防火墙黑名单或者向对方站长发出警告。6. 超越 Referer更安全的防盗链方案探讨当基于Referer的防盗链无法满足安全需求时例如保护付费视频流、独家文档我们需要更强大的方案。6.1 Nginxsecure_link模块该模块需要额外编译--with-http_secure_link_module它通过验证链接中的 MD5 哈希值和过期时间来实现防盗链。工作原理后端程序生成一个带过期时间戳 (expires) 和签名 (md5hash) 的链接例如/protected/file.zip?stEXPIRESeMD5HASH。用户访问这个链接。Nginx 根据配置的密钥 (secure_link_secret) 和请求中的参数重新计算哈希并与传入的e值比对同时检查时间戳是否过期。只有哈希匹配且未过期的请求才会被允许访问。配置示例location /protected/ { secure_link $arg_st,$arg_e; # 从URL参数中获取 st(时间戳) 和 e(哈希) secure_link_md5 $secure_link_expires$uri$remote_addr your_secret_key; # 加密字符串的构成方式 if ($secure_link ) { return 403; # 哈希验证失败 } if ($secure_link 0) { return 410; # 链接已过期 } # 验证通过正常提供文件 alias /path/to/secure/files; }优缺点优点安全性极高链接可过期无法伪造。缺点生成链接依赖后端程序链接一旦泄露在有效期内仍可访问配置较复杂。6.2 基于 Cookie 或 Token 的验证对于 Web 应用更常见的做法是在用户认证后为其会话生成一个临时的 Token。当用户请求受保护资源时需要携带这个 Token通常放在 Cookie 或 URL 参数中。Nginx 可以通过ngx_http_auth_request_module模块向一个认证服务如你的后端API发起子请求来验证这个 Token 的有效性。配置思路location /protected-media/ { auth_request /auth; # 发起子请求到 /auth 进行验证 auth_request_set $auth_status $upstream_status; # 获取认证结果 # 如果认证失败返回非2xx则拒绝访问 error_page 401 403 /login; # 可以跳转到登录页 ... } location /auth { internal; # 内部位置外部无法直接访问 proxy_pass http://auth-backend/check-token; # 传递请求头含Cookie到后端验证 proxy_pass_request_body off; # 不需要传递请求体 proxy_set_header Content-Length ; }这种方式与业务逻辑深度集成是最灵活、最安全的但实现成本也最高。6.3 方案对比与选型建议特性基于 Referer基于 secure_link基于 Cookie/Token安全性低-中可伪造高密码学签名高会话绑定复杂度低Nginx配置中Nginx后端生成高需认证体系性能开销极低低哈希计算中需额外认证请求链接可控性无链接永久有效强可设置过期强可随时吊销适用场景公开内容防普通盗链付费内容、限时下载会员专区、私密文件个人建议公开博客/资讯站用Referer 方案简单有效。资源下载站/图库用Referer 方案配合返回警告图片体验好。在线教育/付费视频用secure_link或Token 方案保障内容安全。SaaS/企业内网应用用Cookie/Token 方案与现有用户系统无缝集成。防盗链不是一个“配置完就忘”的事情。它需要你根据自己网站的内容特性、用户群体和面临的威胁选择合适的策略并持续观察和调整。从最简单的Referer检查开始你已经能抵御绝大部分的流量损耗。随着业务发展再逐步升级到更安全的方案。记住安全永远是一个动态平衡的过程核心是让盗链者的成本远高于其收益。