家庭服务器最容易犯的错,是把“能跑”当成“能运维”。服务装上了、页面能打开,不代表它安全、可恢复、出了问题知道去哪看。我的做法是先定边界:公网只留一个入口,服务默认绑私有地址;然后补三件事——看得见(监控)、管得住(凭据管理)、恢复得了(备份)。
这篇文章不列流水账,而是按这三件事介绍现在这套服务是怎么组织的,重点讲 Beszel 和两种凭据管理:一种管人的密码,一种管机器的密钥。
1. 拓扑:只把入口放到公网
整体是“一台家用主机 + 一台小 VPS”的结构:
公网 │Cloudflare(DNS / TLS / Tunnel / Access) │VPS(Nginx,公网 HTTPS) │ WireGuard 私网家用主机(服务只绑私有地址)家用主机在 NAT 后面,不开放路由器端口转发;VPS 是唯一的公网入口,负责 TLS 终止和按域名反向代理;Cloudflare 负责 DNS、Tunnel 和边缘认证;两台机器之间用 WireGuard 私网连接。动态服务的请求路径是:
浏览器 → Cloudflare → Tunnel → VPS Nginx → WireGuard → 家用主机服务本身只监听私有地址。公网域名由 Nginx 按 Host 转发,Access 按域名分别配置,应用自己再做一层登录。这样一来,家庭公网 IP 不暴露,路由器上不需要开洞,TLS 只在边缘处理,入口少、审计面也小。
代价是链路变长:Cloudflare、Tunnel、VPS、WireGuard 任何一环出问题,公网访问都会断。这也是为什么同一个局域网里还要有一条直连入口——那是下一篇文章的内容。
2. 服务地图
按职责分层,比按容器名罗列更有用:
| 层 | 组件 | 作用 | 暴露方式 |
|---|---|---|---|
| 公网入口 | Cloudflare DNS / TLS / Tunnel / Access | 域名、证书、隧道、边缘认证 | 公网 |
| 边缘反代 | VPS Nginx | 按域名转发到私网服务 | 公网 HTTPS |
| 私网 | WireGuard | 连接 VPS 与家用主机 | 仅隧道 |
| 监控 | Beszel | 主机与容器指标 | Access + 应用登录 |
| 密码库 | Vaultwarden | 人的密码 | 应用登录 + 2FA |
| 密钥 | sops + age | 机器的密钥 | 无网络入口 |
| 对象存储 | MinIO | 图片、资料 | 只读或受限 |
| 照片 | Immich | 照片库 | 应用登录 |
| 备份 | 定时任务 / 手动 | 数据恢复 | 本机 + 离线 |
服务不是越多越好。每加一个,至少要能回答四个问题:数据放在哪、谁能访问、怎么备份、怎么回滚。答不上来的,先别加。
3. Beszel:先让状态可见
Beszel 是一个轻量监控面板,由 Hub 和 Agent 两部分组成。Agent 跑在主机上,负责采集;Hub 存历史数据并提供 Web 界面。这里的 Agent 用 host 网络,通过 unix socket 和 Hub 通信,只读挂载 Docker socket,所以除了 CPU、内存、磁盘、网络,还能看到容器状态。数据落在 SQLite 里,不需要额外数据库。
它解决的是“现在是否正常、趋势有没有变化”:
- 磁盘是不是快满了,内存有没有持续上涨;
- 哪个容器在反复重启,网络流量有没有异常;
- 升级或重建之后,指标有没有恢复。
反向代理有一个必须注意的点:Beszel 的实时指标走 WebSocket。Nginx 要转发 Upgrade、关闭响应缓冲、放长超时,否则页面能打开,数字却不动。这类“看起来正常、实际没数据”的状态,比直接报错更难发现。
日常用法很朴素:每天扫一眼面板;发现异常再去看 docker ps、journalctl 和服务自己的健康检查。容器 Up 不等于健康,端口在监听也不等于业务可用;监控只是线索,不是结论。单主机场景下,Beszel 足够轻,不需要为了监控再上一套 Prometheus + Grafana;告警目前是下一步,先把“看得见”做好。
Beszel 的数据是 SQLite,备份时按 SQLite 的方式做(在线备份或先停服务),不要直接拷运行中的文件。它的重要性低于密码库和照片,但配置和历史趋势有价值,升级前备份一下不亏。
4. 第一种密码管理:人用的密码库
人用的密码放在 Vaultwarden 里。它是 Bitwarden 服务端的兼容实现,Web、手机、桌面客户端都能用,数据存在本机的 SQLite 里。
安全模型要分两层看:
- 密码库本身是端到端加密的。 服务器存的是密文;即使主机被攻破,攻击者拿到的也不是明文密码。
- 登录接口和账号元数据仍然暴露。 主密码、2FA、注册开关、管理面板,这些决定的是“别人能不能登进来”。
所以这里用了主密码 + TOTP/WebAuthn,关闭注册,关闭 /admin,只保留必要的登录入口。没有套 Cloudflare Access,原因和照片服务类似:手机和桌面客户端无法完成浏览器 SSO,套上 Access 反而会把正常客户端挡在外面。补偿手段是把应用自身的认证做扎实,并持续关注 Vaultwarden 的 CVE。
备份是每日一次:对 SQLite 做在线快照,连同附件和密钥材料一起打包,用 age 加密后保留一段时间。这里有两个恢复前提:主密码不能丢,age 私钥也不能丢。前者丢了,密码库打不开;后者丢了,加密备份解不开。主密码丢失无法恢复,这是这类工具的固有代价。
选择自建的代价是可用性和备份都要自己负责;收益是数据在手里,客户端生态也成熟。对个人使用来说,这个交换是划算的,但前提是备份真的能恢复,而不是只存在一个从没验证过的 tar 包。
5. 第二种密码管理:机器用的密钥
机器的密钥不放在密码库里,而是用 sops + age 管理。
做法是:加密后的 env 文件进 git,键名保持明文,值用 age 加密;部署时用脚本解密,渲染成权限 600 的 .env;服务启动只读这个 .env,不依赖任何常驻的密钥服务。这样断网、重启、密钥服务没起来,都不影响业务启动。
为什么不直接用 Vaultwarden?因为机器的密钥需要的是另一套东西:
- 部署脚本要能非交互地取值,不能等人打开网页点确认;
- 密钥要跟配置一起版本化,能看历史、能回滚;
- 服务启动时不能被一个外部服务卡住。
sops + age 的日常操作是“改加密源 → 渲染 → 重建”:
# 改加密源(值在编辑器里临时解密)./scripts/secrets.sh edit <node> <service>
# 渲染成线上 .env(权限 600)./scripts/secrets.sh render <node> <service>
# 检查加密源和线上 .env 是否漂移./scripts/secrets.sh status <node> <service>改完要重建容器才会重新读 env_file;回滚时从 git 里找旧密文,checkout 后重新渲染。age 私钥必须离线保存,丢了加密源就再也解不开;公钥可以入库。SSH 私钥、Cloudflare Token、磁盘恢复密钥不纳入这套机制,各自单独离线管理。
安全上的硬规则:.env 权限 600;加密源之外不出现明文;密钥不进 compose 字面量、不进 docker run 参数、不进命令行历史、不进聊天记录。渲染前会把旧 .env 备份到本地,避免改错之后没有退路。
6. 为什么分成两套
把两套放在一起对比,分工很清楚:
| 维度 | Vaultwarden | sops + age |
|---|---|---|
| 使用者 | 人 | 服务、部署脚本 |
| 交互方式 | Web / 手机 / 桌面,手动 | CLI,部署时非交互 |
| 主要威胁 | 账号被盗、登录接口被撞 | 仓库泄露、主机文件泄露 |
| 恢复依赖 | 主密码 + 2FA + 备份 | age 私钥 + git 历史 |
| 适合放什么 | 个人账号、Wi-Fi、网站密码 | 数据库口令、API Key、服务间凭据 |
人的密码要“随时能取、跨设备同步、有 2FA”;机器的密钥要“版本化、可审计、启动时自动注入”。硬塞进一套,体验和安全性都会变差:把数据库口令放进密码库,部署时要人工取值;把个人密码放进加密 env,又失去了客户端的便利。
两套系统有一个交叉点:age 私钥的离线副本可以放进密码库,同时再留一份 U 盘或异机副本;而密码库的备份本身又用 age 加密。它们互相备份,但运行时互不依赖。这个关系要写清楚,否则真出事时很容易发现“备份的钥匙在备份里”。
7. 备份与恢复
现在的备份策略按数据重要性分层:
| 数据 | 方式 | 恢复要点 |
|---|---|---|
| 密码库 | 每日 age 加密打包 | 主密码 + age 私钥 |
| Beszel | SQLite 复制 | 低优先级,配置和历史 |
| dsh / Paseo 状态 | 停服务后打包 | 会话、profile、凭据 |
| 配置与密钥 | git + 加密 secrets | 仓库 + age 私钥 |
| 对象存储 / 照片 | 镜像 / dump | 数据目录 + 数据库一致 |
原则只有几条:加密、留离线副本、定期做恢复演练、不要直接复制运行中的数据库文件。SQLite 要用在线备份接口或先停服务;Postgres 用 dump,不要拷数据目录。恢复顺序也固定:先主机和网络,再 age 私钥,再渲染 .env,再恢复数据,最后起服务并验证。
单主机是单点,没有高可用;备份是这套系统里唯一“反脆弱”的部分。它不需要很复杂,但必须真的能恢复。
8. 边界与取舍
这套结构有几个明确的取舍:
- 没有高可用。 主机、VPS、Tunnel、家庭网络和电力,任何一环出问题都会影响服务。个人环境可以接受,但要知道边界在哪。
- Access 和应用认证是两层,不互相替代。 Access 挡在边缘,应用认证挡在服务门口;局域网入口会绕过 Access,所以应用认证必须一直在。
- 明文 HTTP 只用于可信局域网。 公网必须 TLS;局域网直连的便利性,是用“信任这个网络”换来的。
- 监控不是万能。 Beszel 看趋势,日志看原因,健康检查看端到端;三者不能互相替代。
- 升级要保守。 镜像固定 digest 或明确 tag,升级前备份,先看 release notes,保留回滚路径。家庭服务没有值班同学,出问题时能退回去比“升到最新”重要。
9. 小结
服务清单可以很长,但运维只需要回答三个问题:状态看得见吗、凭据管得住吗、数据恢复得了吗。Beszel 解决第一个,Vaultwarden 和 sops + age 解决第二个,备份和恢复演练解决第三个。
把这三点做好之后,再加服务就只是加一个目录、一组配置和一条备份,而不是每次都在赌自己记得住。