家用服务器上跑着十几个服务:博客、API、面板、监控……但家里是内网,公网 IP 是个"动态且可能不存在"的东西。怎么让外网安全地访问这些服务?
这个问题我折腾了三年,方案换了两代。这篇文章把演进过程和每代的坑都写下来。
第一代:frp + VPS 中转
最初的架构很经典:
公网客户端 → VPS (固定IP, frps) → 内网 n7 (frpc) → 本地服务
frp 本身非常成熟,配置也就十几行。但用了一年之后,痛点越来越明显:
- 端口管理失控。每加一个服务就要开一个新端口,VPS 的防火墙规则越写越长,哪个端口对应什么服务全靠注释回忆。
- TLS 自己管。每个端口要么裸 HTTP(不安全),要么各自配证书(Nginx 配置爆炸)。
- 单点故障。VPS 挂了,所有服务全部失联。frps 的进程守护、自动重启都要自己写。
- 带宽瓶颈。所有流量都过 VPS 中转,家里上传 100M,实际体验可能只有 VPS 带宽的一半。
最致命的一次事故:frpc 升级后配置格式变了,服务静默失联三天——因为内网没人用,公网访问直接超时,我根本没收到任何告警。
第二代:Cloudflare Tunnel
转折点是我把博客放上了 Cloudflare。既然 DNS 和 CDN 都在 CF 了,为什么不让流量直接走 CF 的边缘网络,而不是先打到 VPS 再转内网?
Cloudflare Tunnel(cloudflared)的架构:
公网客户端 → CF 边缘节点 (自动TLS) ──隧道──→ 内网 n7 (cloudflared daemon) → 本地服务
关键区别:隧道是内网主动向外建立的。家里不需要公网 IP、不需要开任何入站端口——防火墙可以关得死死的。
部署
# 1. 安装 cloudflared(systemd 服务)
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
# 2. 用账号令牌认证(比 tunnel token 更细粒度的权限控制)
cloudflared service --token <TUNNEL_TOKEN>
在 CF 控制台配置 ingress 规则,把域名路径映射到内网地址:
| 主机名 | 路径 | 目标 |
|---|---|---|
| blog.example.com | /* | http://127.0.0.1:3000 |
| api.example.com | /v1/* | http://127.0.0.1:3001/v1/* |
解决第一代的所有痛点
- 端口管理:全部收敛到域名+路径,防火墙零入站规则
- TLS:CF 边缘自动签发和续期证书,内网全程 HTTP 即可
- 可用性:cloudflared 是 systemd 服务,挂了自动重启;CF 侧还有多节点冗余
- 带宽:流量走 CF 的骨干网,不再受 VPS 带宽限制
仍然踩的坑
- 本地调试绕不开。开发时要直连内网端口,隧道只对公网生效。解决:hosts 文件 + 内网域名双套配置。
- 流式响应要小心。SSE、大文件下载这类长连接,CF 默认超时是 100 秒。需要在 ingress 里显式配置
noTLSOrigins和超时参数。 - 日志分散。请求日志一半在 CF 控制台(Analytics),一半在 cloudflared 本地。排障时要两边对照。
安全模型的变化
两代方案的安全边界完全不同:
- frp 时代:安全靠"VPS 防火墙 + 端口不暴露"。一旦 VPS 被打穿,内网拓扑直接暴露。
- Tunnel 时代:安全靠"CF 边缘 + 域名鉴权"。内网没有任何入站入口,攻击面从"一个 VPS"缩小到"CF 的 WAF 策略"。
配合 CF 的 WAF、速率限制、Bot 管理,安全性不是"自己想办法",而是站在一个全球 CDN 的肩膀上。
成本账
- frp 方案:VPS 约 ¥50/月(还要为带宽升级买单)
- Tunnel 方案:CF 免费额度(个人博客流量远够用),VPS 退化为纯备份节点
省钱是结果,不是目的。 真正的收益是架构简化——少一个中转节点,就少一类故障。
结语
内网穿透这件事,工具会一直变(frp、nps、Tailscale、Tunnel……),但原则不变:
- 入站端口能不开就不开
- TLS 交给专业系统管
- 架构每多一跳,故障面就多一分
三年两代方案,最大的收获不是某个工具,而是对"安全边界应该画在哪里"的理解。