DMIT VPS + frp 穿透搭建实战:从零到生产就绪
用一台 $36.9/年的 DMIT 香港小鸡做入口,把 Mac mini 家庭服务器暴露到公网。完整记录 frp 穿透搭建、Caddy 反代、安全加固、踩坑过程。
为什么需要一台 VPS
Mac mini M4 跑着 KodaClaw、PostgreSQL、Redis、MongoDB 一堆服务。但这些服务只能内网访问——没有公网 IP,外面进不来。
方案很直接:买一台便宜的香港 VPS 做入口,用 frp 做内网穿透,把 Mac mini 上的服务映射出去。
核心原则:VPS 只做转发,所有服务跑在 Mac mini 上。VPS 的 1C/961MB 算力不值得跑业务,但做流量入口绑绑有余。
选型
| 项 | 选择 | 理由 |
|---|---|---|
| VPS | DMIT HKG.AS3.T1.WEE | 香港,延迟低,$36.9/年 |
| 穿透 | frp v0.68.0 | 功能全,支持 TLS/STCP/dashboard |
| 反代 | Caddy v2.11.2 | 自动 HTTPS,配置简单 |
| 系统 | Ubuntu 24.04 LTS | 稳定,社区支持好 |
50Mbps 出口带宽对 web 服务够用(实测 6.25MB/s,小请求根本跑不满)。
架构
用户 → DMIT(Caddy:443) → frp TLS 隧道 → Mac mini(实际服务)
- Caddy:HTTPS 终止 + 域名分发 + 自动证书
- frps:接收 frpc 连接,转发 TCP 流量
- frpc:Mac mini 上运行,主动连接 DMIT
搭建过程
1. 系统初始化
标准流程:升级系统、创建用户、配 SSH key、禁密码登录。这部分没什么特别的。
2. frps 配置
关键配置项:
bindPort = 7000
transport.tls.force = true # 强制 TLS
transport.tls.certFile = "/etc/frp/tls/server.crt"
transport.tls.keyFile = "/etc/frp/tls/server.key"
auth.method = "token"
auth.token = "一个足够强的密码"
webServer.addr = "127.0.0.1" # dashboard 只允许本地访问
webServer.port = 7500
allowPorts = [ # 白名单,只允许指定端口范围
{ start = 7100, end = 7300 },
{ start = 35000, end = 35100 }
]
注意几点:
transport.tls.force = true强制所有连接走 TLS,即使 frpc 没配 TLS 也会被拒绝allowPorts白名单比单个端口更灵活,但范围要尽量缩窄- dashboard 绑定 127.0.0.1,不暴露到公网
3. Caddy 配置
test.ai-koda.com {
root * /var/www/test
file_server
reverse_proxy /api/* 127.0.0.1:7220
}
Caddy 的好处是自动申请 Let’s Encrypt 证书,不用管证书续期。一行 file_server 就能 serve 静态文件。
4. 安全加固
这是最重要的部分。一台暴露在公网的服务器,默认就是被扫的命。
fail2ban——两个 jail:
[sshd]
enabled = true
port = 22
maxretry = 3
bantime = 7200
[frps-auth]
enabled = true
port = 7000
logpath = /var/log/frps.log
maxretry = 5
bantime = 3600
sshd jail 用 systemd 后端(Ubuntu 24.04 默认不用日志文件),frps-auth 用文件后端读 frps 日志。
SSH 加固:
PermitRootLogin prohibit-password
PasswordAuthentication no
MaxAuthTries 3
X11Forwarding no
AllowTcpForwarding no
UFW:默认 deny incoming,只开必要端口。端口范围要缩窄,不要一股脑开 8000-9000。
frps 配置锁定:
chmod 600 /etc/frp/frps.toml
chattr +i /etc/frp/frps.toml
chattr +i 让文件不可修改,即使 root 也不能直接改(要先 chattr -i 解锁)。防止被入侵后篡改配置。
踩坑记录
1. fail2ban filter 的 <HOST> 捕获组
fail2ban v1.0.2 的 filter regex 必须包含 <HOST> 捕获组,否则 jail 启动会报错退出:
ERROR Unable to register action for frps-auth: missing <HOST> in regex
正确写法:
[Definition]
failregex = .*login from <HOST> failed.*
错误写法(漏了 <HOST>):
[Definition]
failregex = .*login from \S+ failed.*
2. SSH heredoc 传配置文件的转义问题
通过 SSH heredoc 传递包含特殊字符($、#、!)的配置文件时,变量会被本地 shell 展开。试了几种方案:
| 方案 | 结果 |
|---|---|
SSH + heredoc 'EOF' | 变量不展开,但嵌套引号有问题 |
SSH + heredoc "EOF" | 变量展开,配置被破坏 |
SSH + cat > file << 'EOF' | 引号嵌套出错 |
最终方案:base64 编码传递。
# 本地
CONFIG=$(base64 -w0 config.json)
ssh root@server "echo '$CONFIG' | base64 -d > /etc/path/config.json"
这应该是远程传配置文件的通用兜底方案。
3. Caddy 的 handle_path 不支持通配符
Caddyfile 里想匹配多个路径:
# ❌ 不行
handle_path /ping /redis/* /pg/* {
reverse_proxy 127.0.0.1:7230
}
# ✅ 用多个 handle
reverse_proxy /ping /redis/* /pg/* 127.0.0.1:7230
handle_path 是匹配单个路径的,多路径用 reverse_proxy 直接指定就行。
4. IPv6 和 fail2ban 的冲突
sysctl 加固时禁用了 IPv6:
net.ipv6.conf.all.disable_ipv6 = 1
结果 fail2ban 的 systemd 后端依赖 IPv6 socket,禁用后 sshd jail 启动失败。
解决:去掉 IPv6 禁用配置,保持启用。
5. chattr 锁定后的服务重启
chattr +i 锁定 frps.toml 后,systemctl restart frps 会因为无法写日志而失败。
正确操作:
chattr -i /etc/frp/frps.toml
systemctl restart frps
chattr +i /etc/frp/frps.toml
或者确保日志目录的权限和配置文件分开锁定。
资源占用
最终跑在 DMIT 上的东西:
| 服务 | 内存 | CPU |
|---|---|---|
| frps | ~15MB | <1% |
| Caddy | ~20MB | <1% |
| Shadowsocks | ~5MB | <1% |
| fail2ban | ~15MB | <1% |
| 总计 | ~55MB | <1% |
961MB 内存只用了不到 6%。1C CPU 大部分时间空闲。这台 $36.9/年的小鸡完全撑得住。
总结
- frp + Caddy 是成熟的穿透方案,配置不复杂
- 安全加固不能省,公网机器不加固就是裸奔
- 配置文件传远程用 base64,少踩坑
- fail2ban 的 filter 格式有严格要求,别随手写
- 小机器也能跑大架构,关键是知道每个组件在干什么
下一步计划用 Astro 搭建博客系统,部署在 Mac mini 上,通过 frp 穿透出去。到时候再写一篇。
如果你也想搭类似的穿透架构,可以看看 DMIT 的香港 VPS,延迟低,性价比不错。