WaterNorth's Blog

如何把家里 NAS 的服务安全地放上公网

33 分钟MathLeee

0. 引言 #

在 2025 年的暑假,在家每天虚度光阴的我无意中知道了 NAS 这个东西,小小折腾了一下捡垃圾组了一台 NAS 主机。那个时候的我还什么都不懂——什么是 IPv4/v6,什么又是飞牛、群晖、DDNS、域名解析、反向代理、SSL、异地组网……

大量的新知识在等待着我的探索。

最早期,我的服务端口号全部整整齐齐地暴露在公网 v6 下,就这样在这么高的风险下用了一年。

到了 2026 年也就是今年的暑假,我决定申请开通公网 IPv4。一开始的动机很简单:重庆大学虎溪校区校园网的 IPv6 坏了,而且这一年里一直没修也没有公告,导致我想访问家里的服务器只能靠异地组网——但组网服务一般是付费的,免费的第三方组网都有严格的限流或限速,同时还要承担数据泄露的风险。

于是这个暑假我再次打响了联通的电话。公网开通的流程比想象中还要简单,去小区门口营业厅签了一份申请书,当天下午就开通了

这时询问 AI 的我才知道,我原来的做法是多么不安全。抛开"在公网上暴露所有端口"这种主观上的无知不谈,NAS 系统本身也可能存在漏洞——一旦面板端口直接暴露在公网上,数据就等于是敞开的。这无不警醒着我们在使用 NAS 时的公网安全意识。

本文我将从以下几个方面,讲讲有关家用 NAS 的公网安全我做出的应对。

先说安全边界

飞牛、青龙、Lucky、路由器等管理面板,默认不应直接对整个公网开放。只给自己使用时,优先通过 WireGuard、Tailscale 等 VPN/组网方式访问;确实需要给外部用户提供的 Web 服务,再放到反向代理之后,并叠加 HTTPS、强认证、访问限制、更新与监控。

反向代理能减少直接暴露的端口和服务入口,但不会自动隐藏家庭公网 IP,也不会修复后端应用的漏洞。本文记录的是我的实践过程,不是一份照抄后就能保证安全的万能配置。


1. DDNS:让域名跟得上会变的 IP #

DNS #

在了解 DDNS 之前,首先需要知道什么是 DNS。

DNS 是域名系统(Domain Name System),它是互联网的一个核心服务,负责将容易记忆的域名转换成对应的 IP 地址。就像我们不可能记住每个朋友的电话号码一样,电脑和其他电子设备也不可能记住所有的 IP 地址。

在浏览器中输入一个网站的域名,比如 www.baidu.com,这个请求首先会到达本地的 DNS 服务器。如果它知道这个域名对应的 IP 地址,那就直接返回结果;如果不知道,它会向上游的 DNS 服务器请求帮助,直到找到域名对应的 IP 地址为止。

DDNS #

多数家用宽带不会分配固定公网 IP,这就意味着你的 IP 地址可能经常变动。有些线路还处在运营商级 NAT(CGNAT)之后,根本没有可直接入站的公网 IPv4;这种情况仅配置 DDNS 也无法从公网连回家中。一旦可用的公网 IP 改变,旧的 DNS 记录自然会失效。

那怎么办呢?DDNS 来帮你!

在 DDNS 客户端中配置好域名和 IP 的映射关系后,它会监控你的 IP 地址,一旦发现变化,就通过 DNS 服务商开放的 API 更新解析记录。这样可以让域名持续指向当前地址,但它只解决"地址会变"的问题,不负责打通 NAT、开放防火墙或保护服务安全

DDNS 客户端 #

各家 NAS 现在其实都自带有 DDNS 服务,但是飞牛的功能比较局限。为了方便统一管理,我选择了 Lucky 来做 DDNS,同样出名的还有开源 DDNS 客户端 ddns-go

A / AAAA 记录 #

DNS 里的"记录"分很多类型,每种存不同的东西。你只需要认识两个:

记录类型存什么例子
A域名 → IPv4 地址nas.example.cn → 121.xx.xx.xx
AAAA域名 → IPv6 地址nas.example.cn → 2408:xxxx:xxxx:xxxx

名字来源很直白:A = AddressAAAA 是四个 A,因为 IPv6 地址长度(128 位)正好是 IPv4(32 位)的四倍。

同一个域名可以同时有 A 和 AAAA。双栈客户端会根据系统的地址选择与连接策略在 IPv4、IPv6 之间选择,并不保证所有环境都固定优先 IPv6。如果两种协议都能从公网稳定到达,可以同时配置;如果其中一条链路不通,宁可暂时不发布对应记录,以免造成间歇性访问失败。

在 IP 地址的读取上,v6 和 v4 还有一点细节的差异:

  • AAAA(IPv6)通常可以从网卡读取,但必须筛选地址。 要选可公网路由的全局单播地址,排除 fe80::/10 链路本地地址、ULA 和临时隐私地址。IPv6 没有传统 IPv4 NAT,并不等于没有防火墙;路由器的 IPv6 入站规则仍然必须按需放行。
  • A(IPv4)通常要通过外部接口探测。 具体取决于光猫桥接、路由器拨号等网络拓扑;NAS 网卡通常只有 192.168.x.x 等私网地址,不知道出口公网地址。此时应选「网址/接口探测」,否则可能把私网地址写进公网 DNS。

DDNS 配置流程(以 Lucky 为例) #

前置:域名要托管在有 API 的服务商 #

DDNS 的本质是"自动改解析记录",所以域名的 DNS 必须托管在开放 API 的服务商那里。我用的是华为云 DNS,阿里云、腾讯云、Cloudflare 都可以,Lucky 支持的列表里挑一个顺手的就行。

然后去服务商后台创建 API 密钥(华为云叫 AK/SK)。

提示:不要用主账号的密钥

主账号的 AK/SK 等于整个云账号的完全控制权——DDNS 只需要改一条解析记录,却拿到了能删你所有云资源的钥匙。正确做法是建一个 IAM 子用户,只授 DNS 权限、只开编程访问(不给控制台登录)。

怎么判断手里那对是不是主账号密钥:华为云「我的凭证」页面看 IAM 用户名和账号名是否相同——相同就是主账号本人,是子用户则两者不同。

第一步:在 Lucky 里新建 DDNS 任务,填服务商凭据 #

选好 DNS 服务商,把刚才的 AK/SK 填进去。

Lucky 中华为云 DNS 托管服务商和访问密钥配置界面

第二步:A 记录 —— 常见家宽拓扑使用「网址/接口探测」 #

Lucky 提供了几种获取 IP 的方式,其中有「从网卡获取」和「通过网址/接口探测」。

如果公网 IPv4 终止在光猫或路由器上,而 NAS 只有私网地址,就应选择后者。只有公网地址确实直接配置在 NAS 网卡上时,才适合从网卡读取。

第三步:AAAA 记录 —— 选网卡,但要挑对地址 #

IPv6 通常可以从网卡读取,但必须确认选中的是可公网路由的全局单播地址,而不是链路本地地址、ULA 或临时隐私地址。

但要注意一点:一张网卡上会有好几个 IPv6 地址。系统为了防止别人用固定地址追踪你,会额外生成临时地址(IPv6 隐私扩展)专门用于对外连接,几小时到几天就轮换一次。如果你选中的是临时地址,解析记录过一阵就指向一个不存在的地址了。

所以要在网卡的地址列表里挑那个稳定的、非临时的

第四步:填域名 #

主域名 + 要解析的子域名,保存后 Lucky 会立刻跑一次并写入解析记录。

验证 #

别只看 Lucky 显示"成功",实际查一下解析,对比解析出来的地址你真实的公网地址是否一致。

这一步值得养成习惯——后面我把它做成了自动检查,因为 DDNS 失效的时候不会有任何提示


2. 反向代理:从 N 个入口收成 1 个 #

正向代理 #

要理解什么是反向代理(reverse proxy),自然你得先知道什么是正向代理(forward proxy)。

正向代理是一个位于客户端和目标服务器之间的服务器(代理服务器)。为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求,并将获得的内容返回给客户端。

这种代理其实在生活中是比较常见的,比如访问外国网站技术,其用到的就是代理技术。

有时候,用户想要访问某国外网站,该网站无法在国内直接访问,但是我们可以访问到一个代理服务器,这个代理服务器可以访问到这个国外网站。这样,用户对该国外网站的访问就需要通过代理服务器来转发请求,并且该代理服务器也会将请求的响应再返回给用户。这个上网的过程就是用到了正向代理。

正向代理的用途 #

  • 突破访问限制:通过代理服务器,可以突破自身 IP 访问限制,访问国外网站、教育网等。即,租客可以通过中介,来解决无法联系上房东的问题。
  • 提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,会将部分请求的响应保存到缓冲区中,当其他用户再访问相同的信息时,则直接由缓冲区中取出信息传给用户,以提高访问速度。即,中介手里留存了很多房源信息和钥匙,可以直接带租客去看房。
  • 对目标站隐藏客户端出口 IP:目标站看到的是代理服务器地址,但代理服务商仍然知道客户端地址和流量信息。这不等于匿名,也不会自动让客户端免受攻击。

反向代理 #

明白了直接访问、明白了所谓的正向代理,下面就可以来说说反向代理是怎么回事了。

反向代理是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

我们在租房子的过程中,除了有些房源需要通过中介以外,还有一些是可以直接通过房东来租的。用户直接找到房东租房的这种情况,就是我们不使用代理直接访问国内网站的情况。

还有一种情况,就是我们以为我们接触的是房东,其实有时候也有可能并非房主本人,有可能是他的亲戚、朋友,甚至是二房东,但是我们并不知道和我们沟通的并不是真正的房东。这种帮助真正的房主租房的二房东,其实就是反向代理服务器,这个过程就是反向代理。

对于常用的场景,就是我们在 Web 开发中用到的负载均衡服务器(二房东):客户端(租客)发送请求到负载均衡服务器(二房东)上,负载均衡服务器再把请求转发给一台真正的服务器(房东)来执行,再把执行结果返回给客户端。

所以,反向代理其实是"代理服务器"代理了"目标服务器",去和"客户端"进行交互。通过反向代理服务器访问目标服务器时,客户端是不知道真正的目标服务器是谁的,甚至不知道自己访问的是一个代理。

反向代理的用途 #

  • 隐藏后端拓扑:反向代理可以隐藏后端服务的主机、端口和实现细节。但如果域名直接解析到家庭公网 IP,它不会隐藏这个公网 IP;要隐藏源站地址,需要额外使用可信 CDN、隧道或中转服务。
  • 负载均衡:反向代理服务器可以做负载均衡,根据所有真实服务器的负载情况,将客户端请求分发到不同的真实服务器上。即,二房东发现房主本人很忙,于是找到房主的妻子帮忙处理租房事宜。
  • 提高访问速度:反向代理服务器可以对静态内容及短时间内有大量访问请求的动态内容提供缓存服务,提高访问速度。即,二房东同样有房屋信息和钥匙。
  • 统一安全控制:反向代理可以集中处理 TLS、认证、限流、日志和部分应用层过滤。它能减少后端直接暴露,但不是完整的 WAF 或 DDoS 防护,也不能替代应用更新、最小权限和备份。

到底怎么反代 #

还记不记得我们之前遇到的那个严重的安全问题——把每个服务的端口都暴露在了公网上,这意味着任何知道你公网地址的人都可以尝试访问你的服务及接口。

一种更易管理的做法是:通过反向代理给每个需要公开的服务分配一个子域名,再映射到内部服务端口,让外部流量只经过统一入口。这样能减少直接暴露的服务与端口,但请求最终仍会到达后端,后端的认证、更新和安全配置依然重要。

转折:运营商封了 80/443 #

部分家宽运营商会限制 80/443 入站,具体原因和开放条件取决于当地运营商及合规要求,应以实际线路测试和运营商答复为准。

我的线路无法直接使用标准端口,因此改用非标准端口(外部 8443 → 内部 443)。这能绕开端口限制并减少一些自动扫描噪声,但非标准端口本身不是安全措施,防火墙、认证、更新和监控仍然不可少。

最终的链路是这样:

公网请求 → 光猫(外部 8443 → 内网 443)→ Lucky(按域名分流)→ 各服务的本地端口

反代之后 #

  • 一个 TLS 终端,按子域名分流到内网各服务
  • 安全上的实际变化:公网直接入口从"N 个服务各自的端口"收成"1 个反代"
  • 加新服务不再意味着开新端口

反代配置流程(以 Lucky 为例) #

Lucky 的模型:一条主规则 + 一堆子规则 #

Lucky 的反代是两层结构,理解了这个配起来就很快:

  • 主规则:监听一个端口(我这里是 443),负责收所有进来的请求
  • 子规则:挂在主规则下面,按域名判断该转给谁

所以加一个新服务 = 加一条子规则,主规则和光猫都不用碰。

第一步:建主规则 #

监听 443,开启 HTTPS。

第二步:给每个服务加子规则 #

每条子规则做的事就是一句话:xxx.example.cn 来的请求,转给 127.0.0.1:某端口

Lucky Web 服务中统一监听 443 端口的反向代理规则列表

我这边大致是这样:

域名转发到服务
photo.example.cn127.0.0.1:2283相册
tv.example.cn127.0.0.1:13000影视
doc.example.cn127.0.0.1:18080文档站
nas.example.cn127.0.0.1:5666NAS 面板
xxx.example.cn127.0.0.1:xxxxxxxx

注意:127.0.0.1 只适用于特定部署方式。

只有 Lucky 和后端服务运行在同一主机、同一网络命名空间时,才能用 127.0.0.1。如果服务运行在不同 Docker 容器中,127.0.0.1 指向的是当前容器自己,通常应通过同一 Docker 网络中的服务名和容器端口访问。

无论使用回环地址、容器网络还是内网地址,都应确保后端端口没有被光猫或路由器直接转发到公网,并用防火墙验证公网只剩下统一入口。

证书:为什么必须用 DNS-01,以及它顺便解决了泛域名 #

配 HTTPS 要证书,Let's Encrypt 免费,但要先证明"这域名确实是你的"。有两种验证方式:

  • HTTP-01:CA 来访问 http://你的域名/.well-known/...,看能不能读到指定内容
  • DNS-01:你在 DNS 里加一条特定的 TXT 记录,CA 去查这条记录

在我的网络里 HTTP-01 走不通——Let's Encrypt 的 HTTP-01 校验固定使用 80 端口,而该端口无法从公网访问。TLS-ALPN-01 使用 443,但同样不适合我当前的线路和工具配置。

所以在我的场景里选择 DNS-01。它还有一个额外好处:DNS-01 可以签发泛域名证书。于是我申请了 *.example.cn,以后新增同一级子域名时通常不必重新调整证书。

配置上很省事:Lucky 里填入具备 DNS 记录修改权限的 API 凭据,它会自动完成验证、签发和到期续签。凭据应遵循最小权限原则;条件允许时,DDNS 与证书签发可以使用彼此独立、权限范围更小的子用户或密钥。

Lucky 使用 ACME 和 DNS 验证申请 Let's Encrypt 证书的配置界面

配完之后,加服务变成了什么样 #

对比一下前后:

以前现在
公网入口每个服务一个只保留统一入口
改光猫每次都要不用改
配证书每个服务一遍泛域名覆盖
加 DNS 解析手动去服务商加Lucky 加完规则自动同步

现在加一个新服务就是:填个域名、填个本地端口、保存。 结束。


3. 三层认证:AA / BA / 2FA #

只有前面那个层面的防护当然不够。反向代理主要减少直接入口,并不会自动阻止针对 Web 服务的攻击。在这个基础上我配置了分层认证,重要服务至少要有应用认证和真正的第二因素;管理面板则优先不对公网开放

  • AA(应用认证,本文自定义缩写):飞牛、青龙面板等内置的账号登录
  • BA(BasicAuth):通过 Lucky 的反代配置,可以在端口号之前再加一层登录页
  • 2FA(Two-Factor Auth):要求用户提供两种不同类型的身份验证因子来确认身份,我用的是手机应用生成一次性密码(OTP)

说明:OTP(一次性密码)

TOTP 是根据共享密钥和时间窗口生成的短时验证码,常见时间步长为 30 秒。同一时间窗口内生成的值相同,服务端必须在首次验证成功后拒绝再次使用,才能真正满足"一次性"。

三层结构 #

公网请求
   ↓
[BA]   反代上的 Basic Auth              ← 你自己控制的一层
   ↓
[AA]   飞牛 / 青龙 / Lucky 自带的登录页    ← 应用自带,别人写的
   ↓
[2FA]  动态验证码                        ← 另一类因素

3.1 AA 的局限:它不是你写的 #

各服务本来就有登录页,看着"已经有认证了",但:

  • 强度参差不齐:有的有失败限流有的没有,密码策略也不一样
  • 登录页本身就是攻击面 —— 未认证的请求已经打进应用代码里了
  • 你很难充分审计:第三方应用的认证实现、依赖和更新节奏并不完全由你控制
  • 面板公网可达 = 把几个第三方的登录实现直接摆出去

举个具体的例子:青龙的定位就是"按设计执行任意脚本",所以它的登录页是这几个里风险最高的——破了不是丢数据,是拿到执行权

3.2 BA 的价值:拦在应用之前 #

Basic Auth 加在反代规则上,在规则覆盖完整且不存在绕过入口时,没通过 BA 的请求不会被转发到应用

它可以降低部分未授权漏洞被直接触达的概率,是纵深防御中容易掌控的一层,但不能代替上游补丁。Basic Auth 还必须运行在 HTTPS 之上,并搭配独立强密码、失败限流、日志告警;条件允许时再加 IP 白名单。

警告:BA 可能只挡了页面,没挡 API。

写接口(上传、回滚这类)如果漏在外面,等于把数据的写权限交出去。

验证方法:用 curl 不带任何凭据逐个敲,期望每条都返回 401

别用浏览器验 —— 它会自动带上缓存的认证头,看着一切正常,其实什么都没证明。

3.3 BA + AA 不是「双因素」 #

两者都是「你知道的东西」(密码),叠起来只是两道单因素。

它防的是"某一层的实现有洞",防不了"密码泄露了"。

真正的第二因素是 2FA —— 例如手机验证器中持有的 TOTP 密钥,或安全密钥。2FA 能显著降低密码泄露带来的风险,但 TOTP 仍可能被实时钓鱼;支持 WebAuthn/FIDO2 时,它通常是更抗钓鱼的选择。

3.4 关于 TOTP(2FA) #

飞牛、青龙等都有自带的 2FA 选项,其他一些自建的服务可能没有,则需要自己构建配置 OTP。

理解 TOTP 原理不等于应该自己实现生产认证。 实际验证还涉及密钥安全存储、随机数质量、时间漂移、验证码重放、失败限流、恢复码和账户找回。应优先使用应用自带功能、经过审计的认证代理或成熟库,而不是把下面的算法说明直接改成线上登录代码。

TOTP 是怎么工作的 #

一句话:服务端和你手机各存一份相同的密钥,各自拿"当前时间"去算,算出来一样就通过。

密钥 K(双方共有,只在绑定那一刻交换一次)
计数器 C = floor(当前 Unix 时间 / 30)      ← 每 30 秒变一次
HMAC(K, C) → 动态截断
动态截断 → 取 4 字节 → 对 10^6 取模 → 6 位数字

关键在于手机和服务器之间不需要任何通信——两边输入相同(密钥 + 时间),输出自然相同。所以你飞行模式也能生成验证码

RFC 6238 默认时间步长是 30 秒,默认算法基于 HMAC-SHA-1,也允许 HMAC-SHA-256 或 HMAC-SHA-512。验证端还应限制可接受的时间漂移,并记录已经成功使用的时间步,避免同一验证码在有效窗口内被重复接受。


结语 #

回头看,这一整套东西没有一样是我一开始就规划好的

它是一个一个补出来的:先是发现端口全裸着,才有了反代;反代要 HTTPS,才碰到 80 被封、才知道有 DNS-01 和泛域名;域名要跟着动态 IP 走,才有了 DDNS;DDNS 配错网卡导致时好时坏,才搞明白 A 和 AAAA 的区别;后来觉得一层密码不够,才有了 BA 和 2FA。

每一步都是被上一步的问题逼出来的。

如果要我把这一年多折腾下来最有用的几点留下,大概是这三条:

一、把入口收成一个。 反向代理带来的最大好处不是方便,而是把公网直接入口从"N 个服务各自的端口"收成一个可以统一认证、记录和限制的入口。后端服务仍要及时更新,因为合法流量最终仍会进入应用,代理配置也可能出现遗漏。

二、认证要分层,而且要分清"层"和"因素"。 BA 挡在应用之前,AA 是应用自己的,两者叠加主要防的是"某一层实现或配置出问题";但它们都是密码,加起来仍然只是一种因素。2FA 能显著降低密码泄露后的风险,管理面板不对公网开放则更进一步减少暴露。

三、配完不等于生效。 这是我最晚才意识到的一点——配置错了会报错,配对了但没生效不会有任何提示。DDNS 失效不会告诉你,只是某天突然连不上;Basic Auth 漏了某个接口不会告诉你,浏览器还会用缓存的认证头让你觉得一切正常。所以每加一层防护,最好都想清楚一件事:我怎么证明它现在还在生效?

写这篇文章的时候我特意把域名、内网地址这些都做了脱敏——这本身也算是同一个习惯的延续:拓扑可以讲,标识符不必给。

从"把所有端口整整齐齐暴露在公网"到现在,中间隔的其实不是什么高深技术,只是一次又一次"原来这样是不安全的"。如果这篇东西能让你少裸奔一段时间,那就值了。


参考资料 #

评论

0

评论功能待配置数据库后启用。