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 算力不值得跑业务,但做流量入口绑绑有余。

选型

选择理由
VPSDMIT 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,延迟低,性价比不错。