frp-tunnel — 自建內網穿透
用自建的 frp + Caddy + Hetzner VPS 分享本地開發中的網站。自訂域名、自動 HTTPS、無限 tunnel。
替代方案(無 VPS 時):見 old-share-local-site skill(ngrok/localhost.run)
⚙️ Config(自訂區塊)
使用此 skill 前,請將以下值替換成你自己的環境:
| 變數 | 目前的值 | 說明 |
|---|
| VPS IP | INLINECODE1 | 你的 VPS 公網 IP |
| 域名 |
*.tunnel.fud.city | 你的 wildcard 子域名 |
| DNS Provider | Cloudflare (
fud.city) | 管理你域名 DNS 的服務 |
| GitHub 備份 |
https://github.com/darwin7381/frp-tunnel | 你的 private repo(可選) |
Dashboard 密碼:不記在這裡。在 VPS 的 frps.toml 裡設定你自己的密碼。
基礎資訊
| 項目 | 值 |
|---|
| VPS IP | 5.223.75.160 |
| VPS Provider |
Hetzner Cloud, Singapore, CPX12 |
| 域名 | *.tunnel.fud.city |
| DNS | Cloudflare (fud.city) |
| HTTPS 方式 |
Wildcard cert (DNS-01 challenge via Cloudflare API) |
| frp Dashboard | http://5.223.75.160:7500 |
| GitHub 備份 | https://github.com/darwin7381/frp-tunnel (private) |
| 本地 config | ~/.frp/frpc.toml |
| 本地 tmux session |
frpc |
| VPS services | frps (systemd) + caddy (systemd) |
目前的 Tunnels
| 名稱 | 本地 Port | URL |
|---|
| news-dashboard | 5173 | https://news.tunnel.fud.city |
| oldweb |
8080 | https://oldweb.tunnel.fud.city |
| api | 8000 | https://api.tunnel.fud.city |
| fin-terminal | 5177 | https://terminal.tunnel.fud.city |
| fin-terminal-api | 3002 | https://terminal-api.tunnel.fud.city |
HTTPS 方式:Wildcard vs Per-Domain
VPS 上的 Caddy 有兩種方式為 tunnel 提供 HTTPS。目前使用 Wildcard(推薦)。
方式 A:Wildcard Certificate(目前使用 ✅)
原理:一張 *.tunnel.fud.city wildcard cert 涵蓋所有子域名。使用 DNS-01 challenge — Caddy 透過 Cloudflare API 自動加 TXT record 驗證。
Caddyfile:
CODEBLOCK0
需要:
- - Caddy with Cloudflare DNS plugin(標準版沒有,需重新編譯或下載)
- Cloudflare API Token(Zone:DNS:Edit 權限,只限 fud.city)
- Token 設定在 INLINECODE8
新增 tunnel 只需改本地 frpc.toml,不用動 VPS。
安裝步驟(已完成,僅供紀錄):
CODEBLOCK1
方式 B:Per-Domain Certificate(備用)
原理:每個子域名獨立簽一張 cert。使用 HTTP-01 challenge — Let's Encrypt 訪問 http://xxx/.well-known/acme-challenge/ 驗證。
Caddyfile:
CODEBLOCK2
需要:標準版 Caddy 即可,不需要 API token。
缺點:每次加 tunnel 都要 SSH 進 VPS 改 Caddyfile 再 reload Caddy。
適合場景:
- - 不想給 Cloudflare API token
- 需要不同子域名指向不同 upstream(不只是 frp)
- 臨時測試
如何切換
Wildcard → Per-Domain:
CODEBLOCK3
Per-Domain → Wildcard:
按上面方式 A 的安裝步驟。
日常操作
啟動 frpc(Mac 重啟後需要)
CODEBLOCK4
檢查 frpc 狀態
CODEBLOCK5
加新 tunnel(Wildcard 模式 — 只需改本地)
Step 1 — 本地 config
編輯 ~/.frp/frpc.toml,加一段:
CODEBLOCK6
Step 2 — 重啟本地 frpc
CODEBLOCK7
Step 3 — 驗證(不需要動 VPS!Wildcard cert 自動涵蓋)
CODEBLOCK8
確認回傳 HTTP/2 200 才告訴用戶。
Step 4 — 同步 GitHub 備份
CODEBLOCK9
加新 tunnel(Per-Domain 模式 — 需改 VPS)
同上 Step 1-2,但在 Step 2 和 3 之間多一步:
CODEBLOCK10
前端 Vite 的 allowedHosts
Vite dev server 預設會拒絕非 localhost 的 Host header。加 tunnel 時前端 vite.config.ts 也要加:
CODEBLOCK11
臨時開一條(不改 config)
CODEBLOCK12
Wildcard 模式下,臨時 tunnel 也自動有 HTTPS。
發送 URL 前必做的檢查(SOP)
每次發送 tunnel URL 給用戶前:
- 1. 確認 frpc 在跑 — INLINECODE13
- 確認 proxy 成功 — 看到 INLINECODE14
- 確認回傳 200 — 不是 502/404/連線失敗
- 確認內容正確 —
curl -s https://xxx.tunnel.fud.city | grep "<title>",title 必須是預期的網站名稱 - 確認本地 server 是活的,不是僵屍進程 —
ps aux | grep <port> 檢查進程啟動時間,如果是幾天前啟動的舊進程,很可能已經僵屍化(佔 port 但不正常服務,偶爾回 200 但頁面空白)。殺掉重啟。 - 瀏覽器驗證時禁止信任 cache — 用無痕模式或
curl --resolve 強制走 VPS IP,不要用已開過的 tab
⚠️ 已知 Failure Mode:僵屍進程(2026-03-04 事故)
本地 dev server(如 Vite)長時間不重啟會變成僵屍:佔著 port、偶爾回 HTTP 200,但實際頁面是空白。
curl 檢查看似正常,瀏覽器有 cache 也看似正常,但外部用戶(包含手機)打開是空白。
排查步驟:
- 1.
ps aux | grep <port> — 看進程啟動時間,超過 1-2 天就該懷疑 - 殺掉舊進程,在 tmux 裡重新 INLINECODE19
- 重啟後再 curl + grep title 確認
VPS 故障排除
CODEBLOCK13
防火牆規則(VPS ufw)
HTTP (Caddy + Let's Encrypt) |
| 443 | HTTPS (Caddy) |
| 7000 | frp client 連線 |
| 7500 | frp Web Dashboard |
費用
- - VPS: $7.59/月(Hetzner CPX12 Singapore + IPv4)
- 域名: 已有 fud.city
- SSL: 免費(Let's Encrypt via Caddy)
- 流量: 0.5 TB/月(實際用量約 10-15 GB/月)
frp-tunnel — 自建内网穿透
用自建的 frp + Caddy + Hetzner VPS 分享本地开发中的网站。自定义域名、自动 HTTPS、无限 tunnel。
替代方案(无 VPS 时):见 old-share-local-site skill(ngrok/localhost.run)
⚙️ 配置(自定义区域)
使用此 skill 前,请将以下值替换成你自己的环境:
| 变量 | 当前值 | 说明 |
|---|
| VPS IP | 5.223.75.160 | 你的 VPS 公网 IP |
| 域名 |
*.tunnel.fud.city | 你的 wildcard 子域名 |
| DNS 提供商 | Cloudflare (fud.city) | 管理你域名 DNS 的服务 |
| GitHub 备份 | https://github.com/darwin7381/frp-tunnel | 你的 private repo(可选) |
Dashboard 密码:不记在这里。在 VPS 的 frps.toml 里设置你自己的密码。
基础信息
| 项目 | 值 |
|---|
| VPS IP | 5.223.75.160 |
| VPS 提供商 |
Hetzner Cloud, Singapore, CPX12 |
| 域名 | *.tunnel.fud.city |
| DNS | Cloudflare (fud.city) |
| HTTPS 方式 |
Wildcard cert (DNS-01 challenge via Cloudflare API) |
| frp Dashboard | http://5.223.75.160:7500 |
| GitHub 备份 | https://github.com/darwin7381/frp-tunnel (private) |
| 本地配置 | ~/.frp/frpc.toml |
| 本地 tmux session | frpc |
| VPS 服务 | frps (systemd) + caddy (systemd) |
当前的 Tunnels
| 名称 | 本地端口 | URL |
|---|
| news-dashboard | 5173 | https://news.tunnel.fud.city |
| oldweb |
8080 | https://oldweb.tunnel.fud.city |
| api | 8000 | https://api.tunnel.fud.city |
| fin-terminal | 5177 | https://terminal.tunnel.fud.city |
| fin-terminal-api | 3002 | https://terminal-api.tunnel.fud.city |
HTTPS 方式:Wildcard vs Per-Domain
VPS 上的 Caddy 有两种方式为 tunnel 提供 HTTPS。目前使用 Wildcard(推荐)。
方式 A:Wildcard 证书(目前使用 ✅)
原理:一张 *.tunnel.fud.city wildcard cert 涵盖所有子域名。使用 DNS-01 challenge — Caddy 通过 Cloudflare API 自动添加 TXT record 验证。
Caddyfile:
caddyfile
*.tunnel.fud.city {
tls {
dns cloudflare {env.CFAPITOKEN}
}
reverse_proxy localhost:8080
}
需要:
- - Caddy with Cloudflare DNS plugin(标准版没有,需重新编译或下载)
- Cloudflare API Token(Zone:DNS:Edit 权限,只限 fud.city)
- Token 设置在 /etc/systemd/system/caddy.service.d/override.conf
新增 tunnel 只需改本地 frpc.toml,不用动 VPS。
安装步骤(已完成,仅供记录):
bash
1. 下载带 cloudflare plugin 的 Caddy
curl -s https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fcaddy-dns%2Fcloudflare -o /usr/bin/caddy
chmod +x /usr/bin/caddy
2. 验证 module 存在
caddy list-modules | grep cloudflare
3. 设置 CF token
mkdir -p /etc/systemd/system/caddy.service.d
cat > /etc/systemd/system/caddy.service.d/override.conf << EOF
[Service]
Environment=CF
APITOKEN=你的token
EOF
4. 更新 Caddyfile
cat > /etc/caddy/Caddyfile << EOF
*.tunnel.fud.city {
tls {
dns cloudflare {env.CF
APITOKEN}
}
reverse_proxy localhost:8080
}
EOF
5. 重启
systemctl daemon-reload
systemctl restart caddy
方式 B:Per-Domain 证书(备用)
原理:每个子域名独立签一张 cert。使用 HTTP-01 challenge — Lets Encrypt 访问 http://xxx/.well-known/acme-challenge/ 验证。
Caddyfile:
caddyfile
news.tunnel.fud.city {
reverse_proxy localhost:8080
}
terminal.tunnel.fud.city {
reverse_proxy localhost:8080
}
每加一个 tunnel 就加一段
需要:标准版 Caddy 即可,不需要 API token。
缺点:每次加 tunnel 都要 SSH 进 VPS 改 Caddyfile 再 reload Caddy。
适合场景:
- - 不想给 Cloudflare API token
- 需要不同子域名指向不同 upstream(不只是 frp)
- 临时测试
如何切换
Wildcard → Per-Domain:
bash
ssh root@5.223.75.160
删除 override.conf(或保留也无妨)
把 Caddyfile 改成逐一列出每个域名
systemctl restart caddy
Per-Domain → Wildcard:
按上面方式 A 的安装步骤。
日常操作
启动 frpc(Mac 重启后需要)
bash
SOCK=/tmp/openclaw-tmux/openclaw.sock
tmux -S $SOCK new-session -d -s frpc frpc -c ~/.frp/frpc.toml
检查 frpc 状态
bash
SOCK=/tmp/openclaw-tmux/openclaw.sock
tmux -S $SOCK capture-pane -t frpc -p -S -10
加新 tunnel(Wildcard 模式 — 只需改本地)
Step 1 — 本地配置
编辑 ~/.frp/frpc.toml,加一段:
toml
[[proxies]]
name = new-project
type = http
localPort = 3000
customDomains = [new.tunnel.fud.city]
Step 2 — 重启本地 frpc
bash
SOCK=/tmp/openclaw-tmux/openclaw.sock
tmux -S $SOCK send-keys -t frpc C-c
sleep 2
tmux -S $SOCK send-keys -t frpc frpc -c ~/.frp/frpc.toml Enter
Step 3 — 验证(不需要动 VPS!Wildcard cert 自动涵盖)
bash
curl -sI https://new.tunnel.fud.city | head -5
确认返回 HTTP/2 200 才告诉用户。
Step 4 — 同步 GitHub 备份
bash
cd ~/Projects/frp-tunnel
cp ~/.frp/frpc.toml ./frpc.toml
git add -A && git commit -m Add tunnel: new-project && git push
加新 tunnel(Per-Domain 模式 — 需改 VPS)
同上 Step 1-2,但在 Step 2 和 3 之间多一步:
bash
ssh root@5.223.75.160
编辑 /etc/caddy/Caddyfile,加:
new.tunnel.fud.city {
reverse_proxy localhost:8080
}
systemctl reload caddy
等 30 秒让 cert 签发
前端 Vite 的 allowedHosts
Vite dev server 默认会拒绝非 localhost 的 Host header。加 tunnel 时前端 vite.config.ts 也要加:
ts
server: {
allowedHosts: [xxx.tunnel.fud.city],
}
临时开一条(不改配置)
bash
frpc http --server-addr 5.223.75.160:7000 --local-port 3000 --custom-domain temp.tunnel.fud.city
Wildcard 模式下,临时 tunnel 也自动有 HTTPS。
发送 URL 前必做的检查(SOP)
每次发送 tunnel URL 给用户前:
- 1. 确认 frpc 在跑 — tmux capture-pane -t frpc
- 确认 proxy 成功 — 看到 start proxy success
- 确认返回 200 — 不是 502/404/连接失败
- 确认内容正确 — curl -s https://xxx.tunnel.fud.city | grep ,title 必须是预期的网站名称
- 确认