前阵子用飞牛fnOS给朋友分享大文件包,图省事直接用了系统自带的分享链接,结果不到半天,日志里全是扫描器的访问记录,有的IP还试图把整个目录遍历一遍。更头疼的是,链接被朋友转到一个大群之后,下载流量瞬间把NAS占用拉到80%,后台点什么都卡。后来我学乖了:外链分享想用得省心,前面必须挡一层NGINX反向代理,过滤规则也绝不能省。
这套玩法说白了就是三件事:一是用NGINX把飞牛的共享目录代理成一个独立域名或端口,二是按路径、IP、用户代理、来源等维度过滤请求,三是用频率和带宽限制把单个链接的冲击力降到最低。我自己在飞牛上跑过这套配置,踩过的坑和绕过的弯都写在下面。如果你是那种“系统自带分享不够用,但也不想啃完整本NGINX教程”的人,这篇可以直接帮你上手。
1. 为什么外链分享需要反代过滤——先搞清楚你挡的是谁
1.1 飞牛自带分享链接到底缺了什么
飞牛fnOS作为NAS系统,确实自带“分享链接”功能,生成一个短链,别人打开就能下载。对轻度使用来说这功能没问题,但它本质上只有两种状态:不分享、公开分享。一旦公开,任何拿到链接的人都能访问,访问者的身份、来源、频率完全不受控。
我在实际使用中总结过它的几个短板:
- 没有路径级别控制。你分享的是某个目录,但如果目录结构里还有其他文件,别人用扫描工具试探一下相邻路径,就可能把不该看的也翻出来。
- 没有IP维度控制。内网访问和公网访问共用一套规则,没法做到“内网随便下、公网只能下特定文件”。
- 没有用户代理识别。扫描器、爬虫、批量下载工具和浏览器在UA字段上差别很大,但自带分享不会去区分。
- 没有速率和并发限制。一个链接被扔进几百人的群,同时下载的人一多,NAS立刻满载。
所以,当分享对象不只是关系特别近的三五个人时,自带链接方案往往撑不住。这时候就需要在NAS前面加一道NGINX反向代理,把原本“裸奔”的分享链接变成“受控”的访问通道。
1.2 反向代理在飞牛外链分享里的角色
反向代理常见用途是把不同域名路由到不同后端服务,但在外链分享场景里,它的核心职责是“前置筛子”。请求到了NGINX之后,先按规则判断该不该放行,放行之后再决定能访问哪个目录,能访问之后还要限制下载速度和并发数量。
这里的“过滤规则”不是单一的一条拦截配置,而是从四个维度组合出来的一套组合拳:
第一,路径过滤。明确告诉NGINX,只有/files/前缀路径下的文件允许访问,其他路径一律403。第二,IP过滤。家庭网、办公网这类固定网段直接授权,外部来源即使有链接,也只能进允许的目录。第三,UA过滤。把常见爬虫、下载器、命令行工具的特征字符串挡在门外。第四,限流限速。控制每个IP的请求频率、并发连接数和单连接带宽。
叠加起来的效果就是:链接可以给出去,但能不能访问、能访问到哪一层、访问多快,全在你的掌控中。这才是“过滤规则”这四个字的完整含义。
2. 过滤规则的四个核心维度——配置前先想清楚规则
2.1 路径过滤:只放行你指定的目录
路径过滤是最基础的一层,也是最容易写错的一层。目标很朴素:分享目录只有/vol1/share/public,那我只让所有带/files/前缀的请求进入这个目录,其他请求一律403。
在NGINX里,路径过滤靠location块实现,关键坑点是alias和root容易搞混。
root的意思是:把请求URL的完整路径拼到 root 值后面再去找文件。比如:
location /files/ { root /vol1/share; }此时访问/files/readme.txt,NGINX实际找的是/vol1/share/files/readme.txt。如果文件原本在/vol1/share/public/readme.txt,路径就对不上,必然404。
alias的意思是:location匹配到的部分直接用alias替换。比如:
location /files/ { alias /vol1/share/public/; }访问/files/readme.txt时,NGINX找的是/vol1/share/public/readme.txt。这才是共享目录映射到URL前缀的正确姿势。
路径过滤还有一个顺序问题。NGINX的location匹配分精确、前缀、正则三类,优先级不同。实际写配置时,建议把精确匹配放前面,前缀匹配次之,正则更次之。如果目录下还有需要单独控制权限的子目录,用嵌套location或命名location处理,比堆一堆一级location更容易维护。
注意:路径过滤要反过来想,默认全拒绝,只放行白名单路径。这样即使以后新增location时漏配置,未考虑的路径也是默认安全状态。
2.2 IP过滤:内网高信任,公网低信任
飞牛NAS通常有两类访问者:一类来自家庭或办公室内网,一类来自公网“外部链接”。如果两者用同一个权限,要么内网用户不方便,要么外网用户太宽松。
IP过滤在NGINX里用allow和deny实现,支持CIDR网段。常见思路是先deny all,再allow放行指定网段,也就是白名单模式;反过来也可以先allow all,再deny屏蔽恶意网段。
我在外链分享场景里的建议是“内网优先、外部受限”的折中方案:
location /files/ { allow 192.168.0.0/16; allow 10.0.0.0/8; deny all; }这样,内网用户可以不受限访问全部共享文件,公网用户则被拦在/files/外。想要更细的策略,比如公网只允许下载某个子目录,可以把IP过滤和路径过滤结合:
location /files/private/ { allow 192.168.0.0/16; deny all; } location /files/public/ { allow all; }这里有个容易踩的坑:如果飞牛NAS前面还套了一层路由器端口转发或云服务器反代,NGINX看到的客户端IP可能不是真实访问者IP,而是上一层代理的IP。这就需要配置realip模块,并声明可信的上游地址。用Docker跑nginx时,记得确认镜像包含realip模块,并在配置里加上:
set_real_ip_from 192.168.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on;否则IP白名单会被误伤,也可能放行掉真正需要拦截的来源。
2.3 用户代理过滤:挡掉扫描器和命令行下载器
外链链接一旦暴露,最先“拜访”的往往不是人,而是各种自动扫描工具。它们的请求路径有规律,UA通常也带着明显特征。常见的有curl、wget、python-requests、Go-http-client、Java等。
如果完全不做限制,这些工具会遍历目录、反复试探路径,白白消耗NAS资源。用UA过滤可以大批量拦在外面。典型配置:
if ($http_user_agent ~* "(curl|wget|python|Java|Go-http-client|masscan|sqlmap|nikto)") { return 403; }提醒一句:UA过滤是“低成本、高效率,但不是零误伤”。有些内部下载器用的UA也带curl字样,你可以后面再补白名单,也可以给真正需要的人单独发一个带token的URL绕过过滤。
还有一点,UA过滤不要过度设计。建议只拦截“明显是程序扫目录”的词,不要连Chrome、Firefox、Edge、Safari的版本号都去做匹配,那没有意义,而且容易误杀正常浏览器的请求。
2.4 频率与带宽限流:防止单个链接被刷爆
这是过滤规则里最体现专业度的一层。NGINX内置的limit_req、limit_conn、limit_rate三个指令,分别控制请求频率、并发连接数和单连接下载速度。三者配合,能把一个被疯传的链接压到“很多人同时在点,NAS也不至于被打穿”。
先看请求频率限制,它基于漏桶算法:
limit_req_zone $binary_remote_addr zone=share_limit:10m rate=10r/m;这条配置的含义是:以客户端IP为维度,在10MB共享内存里维护计数,允许的平均速率是每分钟10个请求。突发请求如果超过桶容量,会排队或直接丢弃。实际使用建议追加burst参数:
location /files/ { limit_req zone=share_limit burst=20 nodelay; }burst=20表示短时间内可多容纳20个排队请求,nodelay表示排队期间不额外产生延迟。这个数字不是越大越好,如果共享目录里全是大文件,建议调小一点,避免一次突发把带宽打满。
再看并发连接限制:
limit_conn_zone $binary_remote_addr zone=conn_limit:10m; location /files/ { limit_conn conn_limit 5; }意思是同一个IP最多同时建立5个连接。对浏览器下载来说足够,但如果你知道有人会开多线程下载器,这个值还可以再调小。
最后是带宽限制:
location /files/ { limit_rate 2m; }limit_rate 2m表示单连接最大下载速度为2MB/s。一个链接被转发到几百人群里时可以限制单连接速率,能显著降低NAS出口带宽压力。也可以按文件类型分包,比如大文件压到1m/s,图片小文件放开限制。
这三条配置不是三选一,而是叠加生效。我在实战中都是同时开的。算一笔账:假设家用NAS出口带宽是50M,约等于6.25MB/s,每条连接限2m/s,最多只能有3条大文件连接同时跑满;加上每个IP的5连接并发限制,就算10个不同IP突发,也就20个连接,NAS完全扛得住。
3. 落地配置:在飞牛fnOS上跑起一套完整反代过滤规则
3.1 飞牛上安装NGINX的三种方式
在飞牛上装NGINX,我试过三条路,各有优劣。
第一种是直接用系统包。飞牛底层是Linux系统,SSH登录后执行apt install nginx,装出来的是系统版本,适合不想引入额外容器的用户。缺点是版本升级依赖系统源,配置文件与飞牛系统状态耦合度相对高。
第二种是Docker容器。我自己用的就是Docker方案,镜像用的是nginx:alpine,轻量且环境隔离,后期升级、换版本、恢复配置都干净。典型运行命令是:
docker run -d --name nginx-share \ --restart unless-stopped \ -p 443:443 -p 80:80 \ -v /vol1/share/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /vol1/share/public:/var/www/share:ro \ nginx:alpine这里用到了飞牛常见的存储目录结构:/vol1/share是共享区的父目录,/vol1/share/nginx放nginx配置文件,/vol1/share/public是要分享的文件实体。全部用只读挂载,避免nginx容器误改共享目录权限。
第三种是通过飞牛自带的Docker管理界面操作,不需要写命令。在界面上创建容器,映射好端口和卷就行。但如果你后面需要配置realip、limit模块,建议还是用命令行方式起容器,方便指定挂载和参数。
3.2 一套完整可用的反代过滤配置
配置落地时,我把所有业务规则放在一个server块里。下面这套配置是我当前在飞牛实际使用的一套简化版,注释都标在关键位置:
server { listen 443 ssl; http2 on; server_name share.yournas.example; # 证书路径 ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; # 在server块顶部定义限流区 limit_req_zone $binary_remote_addr zone=share_limit:10m rate=10r/m; limit_conn_zone $binary_remote_addr zone=conn_limit:10m; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; # 主分享目录 location /files/ { alias /var/www/share/; index index.html; # 防盗链 valid_referers none blocked server_names *.yournas.example; if ($invalid_referer) { return 403; } # IP白名单 allow 192.168.0.0/16; allow 10.0.0.0/8; deny all; # UA过滤 if ($http_user_agent ~* "(curl|wget|python|java|Go-http-client)") { return 403; } # 三类限流限速 limit_req zone=share_limit burst=20 nodelay; limit_conn conn_limit 5; limit_rate 2m; autoindex off; charset utf-8; } # 其余路径全部拒绝 location / { return 403; } } server { listen 80; server_name share.yournas.example; return 301 https://$host$request_uri; }这份配置看起来不长,但每一行都有含义。alias让/files/readme.txt直接映射到共享目录根;deny all配合前面的allow形成白名单主体;limit_req等三条限制保证资源消耗可控;valid_referers结合$invalid_referer形成基础防盗链层。
需要说明的是,nginx的if指令在location里有很多限制,不建议在里面做复杂逻辑。上面两个if都只做return 403,这是官方文档认可的安全用法。如果想做复杂判断,比如“IP在A网段且UA不是浏览器才拦截”,应该走map或geo模块,而不是堆if。
3.3 防盗链与几个隐藏小技巧
外链分享最头疼的场景之一就是链接被嵌入到别人的网页里,叫盗链。防盗链的核心依据是Referer请求头。上面配置中:
valid_referers none blocked server_names *.yournas.example; if ($invalid_referer) { return 403; }none表示允许空Referer(人从地址栏直接打开没有Referer);blocked表示允许被浏览器或代理剥离掉完整信息的Referer;*.yournas.example表示允许来自自己域名的请求。其余来源的Referer都会被判定为invalid_referer,直接403。
提醒一句:防盗链防不住专业盗取,因为可以伪造Referer。它防的是“不懂技术的人把链接贴到网页里导致流量被白嫖”的低级场景。更严格的场景,建议给每个分发对象生成带token的独立链接,用secure_link模块实现,但那是进阶玩法,一般个人分享用不到。
还有两个容易被忽略的细节:一是中文文件名必须在location块加上charset utf-8;,否则浏览器下载中文文件会乱码;二是分享目录不要开启目录浏览,autoindex off是底线,否则等于把所有文件名主动暴露给扫描器。
3.4 HTTPS证书别拖到“有空再说”
很多用飞牛的朋友配置反代时,第一反应是“先用HTTP把功能跑通”。但外链分享一旦走HTTP,抓包就能看到完整URL路径,如果链接里带查询参数或token,等于直接泄露访问凭证。
强烈建议从第一天就上HTTPS。飞牛系统自带证书入口,也可以在nginx里挂Let's Encrypt证书。配置区别主要就是前面server块里的ssl_certificate和ssl_certificate_key两行。证书过期后用certbot renew重新签即可。如果nginx跑在Docker容器里,有个额外问题:容器内部要能看到证书文件位置,需要把证书目录通过volume共享给nginx容器。
证书这块没太多可讲的,关键是别拖。我见过太多人先跑HTTP,等链接被转发、被记录之后再来补HTTPS,链接一换就全部失效,前面的分享等于白做。
4. 配置完成后如何验证与问题排查
4.1 用curl模拟真实请求测试过滤规则
配置写完,第一件事不是打开浏览器,而是先跑一遍模拟请求。curl最方便,能自由设置UA、Referer、来源IP,模拟各种被拦截的场景。
先测正常请求:
curl -I https://share.yournas.example/files/readme.txt不加-A参数时,curl默认UA是curl/x.x.x,会被上面的UA过滤直接403。所以这个测试返回403反而是正确的。想模拟正常浏览器:
curl -I \ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \ -H "Referer: https://share.yournas.example/" \ https://share.yournas.example/files/readme.txt这条命令返回200,说明路径、IP、UA、防盗链都通过。接着测恶意UA:
curl -I -A "python-requests/2.31.0" https://share.yournas.example/files/readme.txt正常情况下应该返回403。测IP过滤则用X-Forwarded-For伪造头,验证realip模块是否生效:
curl -I -H "X-Forwarded-For: 1.2.3.4" https://share.yournas.example/files/readme.txt如果外网IP1.2.3.4访问被403,换成内网网段能200,说明IP白名单和realip配置都正常。
4.2 常见问题速查表
实际配置过程中几乎每个人都会碰到下面几个问题,我整理成了速查表:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 所有请求都403 | alias路径不对,目录不存在 | 检查容器内/var/www/share是否有文件,看error.log |
| 内网访问也被拦 | realip配置缺失,nginx看到的是路由器IP | 确认set_real_ip_from覆盖了路由器/上级代理网段 |
| 中文文件名乱码 | 缺少charset utf-8; | 在对应location块补上charset utf-8; |
| 新上传的文件访问404 | 挂载卷只读或文件权限不对 | 检查容器挂载参数和宿主机文件权限 |
| 下载速度很慢 | limit_rate设置过小 | 调大限速,例如limit_rate 10m; |
| 大量请求但没触发限流 | limit_req_zone定义位置不对 | 确认zone定义在server块或http块顶部 |
| 浏览器直接打开403 | 防盗链把none误伤 | 确认valid_referers里有none和blocked |
这些坑,我在前两次配置里几乎全踩过。尤其是realip问题最隐蔽:飞牛如果做的是路由器端口转发,nginx看到的客户端IP永远是路由器LAN地址,不做realip处理的话,IP白名单要么全部失效,要么把所有外部请求都当成内网IP。
4.3 两个容易被忽略的日志排查技巧
排查别只盯着结果,日志里有大量信息。NGINX有两个日志:access.log记录请求,error.log记录错误。
建议把外链分享的访问日志单独拆出来。在http块里加一个独立的access_log路径,专门记录/files/下的请求,然后用awk或goaccess分析前十个IP、前十个UA、访问最多的路径。这些数据能非常直观地告诉你,过滤规则到底挡掉了多少无效访问。
实操时可直接看实时日志:
tail -f /vol1/share/nginx/logs/access.log观察时重点看状态码分布。如果403比例异常高,说明规则过于严格;如果200比例高但请求频率平稳,说明规则达到了“该放的放、该挡的挡”的效果。
5. 几个实际使用中的配置心得
分享几个不是第一次配置就能想到的点。
关于目录权限。NGINX进程要以只读方式访问共享目录,但共享目录里往往有多个用户创建的文件,权限很容易对不上。Docker方案里,我建议把要分享的目录单独放到一个专用文件夹,并设置好uid/gid,让容器进程有读取权限。建议不要直接用chmod 777整个目录,那会让权限管理失去意义。
关于限速限流参数。实际没有放之四海皆准的数值。建议先设一组保守参数(比如每分钟10次请求、并发5连接、每连接2m/s),跑三天后看access.log里的被限流数量,再决定调高还是调低。限流不是越狠越好,限得过死,正常朋友下载也会频繁断连,那就不是分享,是折磨。
关于日志保存。我把nginx的日志目录也映射到宿主机独立目录,容器重建后历史日志不会丢。排查问题时,日志是最可靠的证据。
这套NGINX反向代理过滤规则,我在飞牛上跑了几个月,最大的体会是:外链分享的安全性不是靠“秘密链接”换来的,而是靠“公开链接+严格过滤”换来的。链接迟早会扩散,但只要路径、IP、UA、频率、带宽、证书这六道关卡在,扩散出去也只会撞上一堵堵的墙。
如果以后想玩得更深,可以在这个基础上加secure_link给下载链接签名、限制有效期;也可以把反代配置放到前端的软路由或独立服务器上,让nginx不再只是NAS里的一个容器,而是家庭网络的统一入口。那是另一个话题了,但底层思路一样:公开但不裸奔,分享但有边界。