返回博客
官小西

把生产搬回家:Tailscale 打通家庭 NAS 与云服务器实录

家宽没有公网 IPv4 的人对这种局面都不陌生:群晖 NAS 上 7TB 的盘、能跑 Docker 的机器,出了家门就只剩 QuickConnect 中继一条慢路;而云上一台 2C2G 的小服务器,扛着任务板、博客、推送服务全部生产。一边是大量闲置的算力被锁在家里,一边是云上捉襟见肘的内存。2026-09-16 这一天,我在两个 agent 会话(上午 Claude Code、下午 ZCode)里把这件事整个翻了过来——四台设备加云服务器织进同一张 Tailscale 网络,生产搬回 NAS,云只做门口。

先把一天的三个阶段放在这里,后面逐段展开:

阶段 时间 执行方 干了什么
组网打底 上午 Claude Code 四端接入 tailnet、NAS 建裸仓 git 服务、光猫加 UDP 静态映射,离家直连成立
云入网与一次撤退 中午 ZCode 云服务器加入 tailnet(直连 10ms)、试建 NAS 公网入口后主动拆除
生产回家 下午 ZCode 任务板 + 博客迁入 NAS Docker、数据库全量迁移、云上清理;傍晚 Claude Code 把 git 主仓也迁进 NAS 裸仓

上午:组网打底,一条 UDP 映射值 125 倍

家庭自托管的第一性问题是连通性。我的设备底数:Mac(随人移动)、Windows 台式机 prometheus 和树莓派 spider(固定在家)、群晖 DS923+(DSM 7.3.2,AMD R1600,7TB)。装 Tailscale 本身没什么好写的——Mac 走 brew install --cask tailscale-app,Windows 走 winget,树莓派官方脚本一键装。真正有两个坑的都在 NAS 上:

群晖套件中心搜不到 Tailscale,官方分发在 pkgs.tailscale.com/stable/,按架构取 tailscale-x86_64-<ver>-...-dsm7.spk 手动安装,装前核对官方 .sha256。装完 Web 面板经常不出登录链接,CLI 才可靠:

ssh -t nas 'sudo /var/packages/Tailscale/target/bin/tailscale set --operator=guancyxx'
ssh -t nas 'sudo /var/packages/Tailscale/target/bin/tailscale up'

--operator 是一次性开关,开了之后 status/netcheck 不再要 root。

四台设备入网后,离家访问能通,但走的是 DERP 中继。实测(2026-09-16,Mac 挂手机热点):RTT 420ms(峰值 900ms)、吞吐约 17KB/s,git ls-remote 要 7.7 秒——这个速度下 56MB 的仓库 clone 要近一小时。而且最近的中继节点在洛杉矶,掉上去非常难受。打了 25 次 ping / 60 秒,始终没有升级成直连,这不是"等一等就好"的问题。

症结在家用光猫(中国电信智能网关):普通账号 useradmin 的界面里 UPnP 和端口映射都被藏起来了,要用超级管理员账号进后台加一条静态映射——UDP 41641 → NAS 的 41641(Tailscale 默认端口)。两个细节:

  • 别用 DMZ。我们需要的是精确暴露一个 UDP 端口,DMZ 会把 DSM 5000/5001、SMB 445 全部裸到公网。
  • tailscale netcheckPortMapping: 字段不会反映这条手动映射——它只认 UPnP/NAT-PMP/PCP 协议协商的结果。拿它验证映射配没配对,会得到假阴性。

映射生效后同一部手机热点实测:RTT 42ms、吞吐 2.1MB/sgit ls-remote 0.55 秒、56MB 全量 clone 27 秒。一个端口的杠杆,换来了约 125 倍的吞吐提升:

DERP 中继与直连的性能对比:RTT 420ms 到 42ms,吞吐 17KB/s 到 2.1MB/s

这一步还顺手解决了另一件事:NAS 的 /volume1/git 目录 2025 年 3 月就建好了,一直在吃灰。DSM 自带 git 2.39.1,git init --bare 就是服务端。这里的坑全在 SSH:必须先在控制面板启用「用户主目录服务」,否则家目录不存在、authorized_keys 无处安放、ssh-copy-id 显示成功但登录照样失败;然后家目录权限 0711.ssh 目录 0700、密钥文件 0600,群晖的 sshd 对此检查极严,过宽就无声忽略。另外一个容易误判的点:DSM 默认关闭 SFTP subsystem,scp/rsync 都会失败,传文件用 cat 本地文件 | ssh nas 'cat > 远端'——但这不影响 git over SSH,它走 git-upload-pack,不依赖 SFTP。

到这里,Obsidian 知识库开始走 NAS 裸仓做四机同步(同一套思路此前在多机同步方案拆解里推演过,当时推荐 GitHub 做主干、否决了自建——这次实施把这个结论也一并推翻了,知识库里的原文已就地更正)。

中午:云入网,与一次活了几小时的公网入口

网络打通后自然的想法是反过来利用它:云服务器有固定公网 IP,把它和 NAS 连起来,NAS 当服务器、云当出口网关如何?

ZCode 会话把云服务器也装上 Tailscale 加入同一张 tailnet。IDC 机器有公网地址,NAT 打洞毫无悬念,tailscale ping 实测云 ↔ NAS 直连 10ms——到这一步,"云当门面、NAS 当机房"的全部管道已经就位。

然后是第一个决策点:要不要把 DSM 挂到公网?当时真的建了——泛解析域名指过来、nginx 反代 DSM、Let's Encrypt 证书、Basic Auth 双层认证,全链路验证通过。当天下午就拆了。理由值得写下来:

  1. 自己的设备根本不需要这个入口。四台设备全在 tailnet 里,访问 NAS 是直达 100.x 地址的一跳,比绕公网更快;
  2. DSM 是管理面,不是服务面。把它暴露公网等于把整台机器的最高权限接口放在扫描器的视野里——当天 nginx 日志里已经躺着多条境外 IP 对 443 的探测记录,双层认证只是降低概率,不是消除攻击面;
  3. 需要公网访问的应该只有具体的服务,而服务可以走"云网关 → 隧道 → NAS 容器"的路径,管理面永远不出门。

这个"建了又拆"不是返工,它把安全边界想清楚了:NAS 不上公网,上公网的只有经过云网关代理的具体服务端口。下一个阶段的所有设计都建立在这条边界上。

下午:生产回家

架构定型后,迁移本身是标准动作,拓扑长这样:

最终架构:公网请求经云 nginx TLS 终结后走 Tailscale 隧道到 NAS 上的 Docker 容器,数据库与 git 裸仓都在 NAS 本地

几个值得记录的工程决策:

入口只换一行上游。 云 nginx 对 tasks.guancyxx.cn 原来是"本地静态根 + /api 转发本地服务"的分裂结构,迁移后整站收敛为一个 proxy_pass 指向隧道对端的 web 容器——静态分发和 API 分流一并下沉到 NAS 容器里。云上配置从"部署的一部分"变成"纯管道",这意味着以后改应用不再需要动云。

部署哲学原样平移。 云上原来的自动部署有一套 marker 门控:.deployed-commit 文件记录的是"实际部署且健康检查通过"的 commit,不是 git HEAD——服务器上手动 git pull 骗不过 skip 检查;任一步失败 marker 不动,下一轮自动重试。这套逻辑在 NAS 上用 docker compose 重写了一遍,语义完全一致:PR 合入 main 后 ≤15 分钟自动上线。博客的同款脚本每 2 小时跑一次。迁移部署体系时最该保真的不是脚本,是这些不变量。

数据迁移走三件套。 SQLite 在 WAL 模式下,taskdeck.db-wal-shm 三个文件必须一起搬(WAL 里可能有 1.7MB 未合并进主文件的数据),搬完逐字节核对大小再起容器。任务板全量数据:662 任务 / 23 项目 / 8 用户 / 718 评论 / 2670 条活动记录,schema 停在 alembic 0005,一个不丢。

发布链路最后也回了家。 傍晚的 Claude Code 会话把博客主仓从 GitHub 迁到 NAS 裸仓(/volume1/git/guancyxx.cn.git),NAS 部署目录的 origin 直接指向本地裸仓库路径——fetch 不出机器,push 不出国门:

发布链路:本地 push 到 NAS 裸仓库,cron 自动部署脚本 fetch 后构建镜像并健康检查,全程不出家庭网络

云上收尾:停用旧 systemd 服务与旧容器、清掉 13 个停止的容器和 3 个镜像、删旧部署目录。现在那台 2C2G 机器上只剩三个活物:nginx、Tailscale、ntfy。内存占用 365Mi,磁盘释放约 2GB(2026-09-16 实测)。

踩坑合集

这一天的坑密度罕见地高,而且横跨两个 agent 会话,值得单独成节。按"当时看到的现象 → 真相"排列:

# 现象 真相与修法
1 NAS 容器绑 Tailscale IP 报 cannot assign requested address 群晖 Tailscale 跑在 userspace-networking 模式(无 tailscale0 接口)。上午的会话早就观察到"NAS 上没有网络接口、入站被代理到回环",下午的会话撞上 docker 绑定才把这 Observation 变成了结论。修法:绑 0.0.0.0,入站由 tailscale 转发到 localhost。代价见第 7 条
2 固定在家的机器走 Tailscale 访问 NAS 反而 345ms 光猫 hairpin:流量绕到公网端点再回来。同 /24 的机器直接用局域网 IP,SSH 配置按"移动机器走 MagicDNS、固定机器走局域网"分流
3 覆写 DSM 的 docker daemon 配置后,老镜像全部"消失" DSM 的配置文件是 dockerd.json(不是 docker.json),且 data-root/storage-driver: btrfs 等字段必须保留——镜像加镜像源要合并进原配置而不是重写
4 NAS 拉不动 Docker Hub 家宽直连 registry 被墙。daemon 配 registry-mirrors 走国内镜像源后正常
5 改了 nginx 配置 reload 后行为没变 Ubuntu 的 sites-enabled/ 里是独立文件而非软链sites-available/,改错了文件。改成规范软链后修复
6 容器重建后 /api 全线 502 nginx:alpine 的 proxy_pass http://api:8321启动时解析一次域名就写死,api 容器每次部署重建会换 IP。修法:resolver 127.0.0.11 valid=10s + 变量形式 proxy_pass 动态解析
7 userspace 模式的三个隐性代价 吞吐低于 TUN 模式;NAS 不能当子网路由器或出口节点;所有基于来源 IP 的 NAS 防火墙规则对 Tailscale 流量失效(都长得像 localhost)——绑 0.0.0.0 的端口实际暴露给了整个局域网,家用网内可接受,但要知道自己在接受什么
8 NAS 上 nc 扫端口"全是 closed"、ping 报错但退出码仍是 0 nc 根本不存在、ping 非 root 无权限——工具本身是坏的,产出的判定全是假阴性。陌生机器上先用已知开放端口做 sanity check 再信结果

第 8 条值得多写一句,因为它是元教训:那天在 NAS 上排查连通性时,"端口全关"的假结果把方向带偏了很久。任何用工具产出做判据的自动化流程,都应该先验证工具本身可用——对人是这样,对 agent 也是这样

什么该搬,什么不该搬

搬完之后说判断。这套架构成立的前提,是流量画像匹配:

适合搬回家的:任务板、博客这类小流量 web/API——单请求 KB 级、日活两位数,家宽上传带宽绰绰有余;git 裸仓——push/pull 天然稀疏,直连 2.1MB/s 之后完全无感;以及一切"只有自己用"的服务,tailnet 直达比任何公网链路都快。

不该搬的:对可用性有强承诺的东西。这个架构的可用性上限是家庭供电 + 光猫 + 上游宽带的乘积,停电、运营商割接、光猫死机,生产就断——它换不来云上的 SLA,只能靠"云入口还在、只是后端不可达"的降级体验兜底。同理,大流量下载类业务也不适合:所有公网流量都要走一遍"家宽上传 → 隧道 → 云出口",几 Mbps 的云带宽和家宽上传是两道天花板。

还有一个当天显形的问题:生产数据全量回到 NAS 之后,备份从"云厂商的事"变回"自己的事"。目前只有迁移时刻的一份快照躺在数据目录旁边(同盘同机,严格说不算备份)。NAS 级的定时备份(Hyper Backup 或 SQLite 文件级工具)是这套架构补齐前不敢称为完成的部分——这个问题的严重性,和当初审视 agent 数据安全时是同一个量级。

结论

一天,两个 agent 会话,一张 tailnet,最后剩下的是一条干净的链路:公网 → 云 nginx(TLS 终结)→ Tailscale 直连隧道(10ms)→ NAS Docker → 本地数据库与裸仓。云服务器从"全部生产"降级为"一个门口",7TB 的家用存储第一次同时承担了服务与数据两个角色。

回头看,这一天里最有价值的不是任何单项技术,而是两个决策:光猫上那条精确到单个 UDP 端口的静态映射(而不是图省事开 DMZ),以及"NAS 不上公网、只有具体服务经云网关代理"的边界。前者决定了性能上限,后者决定了安全下限。中间所有的坑,本质上都是在给这两个决策补细节。

参考资料

  1. Tailscale — 官方站点与文档
  2. Tailscale — Synology 平台 spk 官方分发目录(按架构选择 -dsm7 包并核对 .sha256
  3. Docker — Daemon 配置与 registry-mirrors 文档
  4. nginx — 官方文档