编辑
2026-04-26
Mac
00

目录

0、先给小白扫个盲(老手跳过)
VPS?小鸡?
Cloudflare 是啥?CF Edge 又是啥?
SSH 别名(~/.ssh/config)
SSH 免密登录(公钥认证)
ProxyJump(跳板机)
反向 SSH 隧道(ssh -R)
背景
1、先找出瓶颈到底在哪
2、第一个方案(失败了):在 JP 也装 cloudflared
3、反向 SSH 隧道登场(快 5 倍)
4、落地步骤(照抄能跑版)
4.1 JP 端:创建受限 tunnel 用户
4.2 mini 端:生成专用 key
4.3 JP 端:写入公钥,加三重限制
4.4 mini 端:autossh + launchd 自愈守护
4.5 客户端:一段 SSH config 搞定
5、踩坑实录(不看白不看)
坑 1:restrict 默认连 port-forwarding 也禁了
坑 2:SSH config Host 行前不能缩进
坑 3:VNC LocalForward 不生效
6、最终效果
7、日常怎么用
8、需要准备的软件 / 服务 / 账号清单
总结

本文涉及的 IP、域名均为示例,请按自己实际情况替换

  • JP 服务器 IP:203.0.113.42(RFC 5737 文档保留段,不是真实地址)
  • 自己的域名:ssh.example.com
  • launchd 标签:com.example.reverse-tunnel-jp

0、先给小白扫个盲(老手跳过)

这篇文章会反复出现一些黑话,先用一段把它们讲明白,不然后面看得云里雾里。

VPS?小鸡?

VPS = Virtual Private Server,虚拟专用服务器。人话就是"在云服务商(腾讯云、AWS、甲骨文、搬瓦工这种)家租一台 Linux 机器,24 小时开着,给你一个公网 IP"。

"小鸡"是 VPS 圈的黑话,一台便宜 VPS 就叫"一只小鸡"。我这台租在日本机房的就被我叫做 JP 小鸡,一个月也就几十块。

Cloudflare 是啥?CF Edge 又是啥?

Cloudflare(简称 CF):全球最大的 CDN 网络服务商之一,免费服务多得离谱,个人玩家人手必备。

CF Edge(边缘节点):Cloudflare 在全球几百个城市都有机房(专业术语叫 POP),你访问 CF 服务的时候会被自动分到"离你最近"的那个机房,这就叫 Edge。你本地 ping cloudflare.com 只有十几毫秒就是这个原因。

CF Tunnel(内网穿透):Cloudflare 送的免费内网穿透服务。以前你想从外网连家里的服务,得搞公网 IP、端口映射、防火墙、动态 DNS,一堆折腾。CF Tunnel 直接让家里跑一个 cloudflared 进程主动连出去到 Cloudflare,然后你从外面访问 ssh.example.com 的时候,Cloudflare 就通过那条隧道把请求扔回家,中间你家路由器不用开一个端口,安全又省事。

SSH 别名(~/.ssh/config

文章里我经常写 ssh jp 就能登录日本服务器,不是魔法,是在 ~/.ssh/config 里配了别名:

Host jp HostName 203.0.113.42 User root IdentityFile ~/.ssh/id_ed25519

以后不用敲 ssh -i ~/.ssh/id_ed25519 [email protected] 这种又长又容易错的命令,直接 ssh jp。后面的 minimini-cfmini-jp 都是别名。

SSH 免密登录(公钥认证)

每次连服务器都输密码太烦,正规玩法是"公钥/私钥对":

  1. 本地生成一对密钥:ssh-keygen -t ed25519
  2. 公钥 ~/.ssh/id_ed25519.pub 内容复制到远程机器的 ~/.ssh/authorized_keys
  3. 以后 ssh 服务器 自动登录,不用密码

这篇文章里所有 SSH 操作默认都是免密的,不然 autossh 守护进程根本跑不起来。

ProxyJump(跳板机)

ProxyJump jp 的意思是:连某台机器之前,先经过 JP 中转一下。一行配置搞定多级跳板。常用场景:目标机器没公网 IP,但能从 JP 那头连到它。

反向 SSH 隧道(ssh -R

划重点,这是整篇文章的主角。

普通 SSH 是"你主动连别人"(正向)。反向隧道反过来:内网机器主动连出去到公网机器,然后把自己的端口映射到公网机器上。公网机器上就多了一个"反过来的端口",谁连这个端口等于连到内网机器。

打个比方:mini 在家里连不到,但它能主动"打电话"给日本小鸡,打通之后这通电话就不挂了。你想联系 mini,就先拨日本小鸡,接通以后请对方把你的话"递"给那头还没挂断的 mini。整条链路就是这么个原理。

名词扫盲结束,下面开始正事。

背景

家里那台 Mac mini 搬到家之后一直用 Cloudflare Tunnel 远程连,免费、稳定、还不用折腾端口映射,看着挺香。直到有一天我出差了,想远程 scp 一个几十 MB 的文件回来,眼睁睁看着进度条像蜗牛爬。

顺手 time 了一下:

scp mini-cf:/tmp/testfile_50m . → 50 MB 花了 1 分 46 秒 → 换算一下,0.48 MB/s

这速度,传个项目代码都能泡杯咖啡回来还没传完。

我不死心,换了几个网络环境试,都是这个速度。那就不是我家网络的锅,CF Tunnel 本身有问题。

正好我在日本租了一台小鸡(常年跑加速用),本地连 JP 快得飞起,那能不能让 JP 当跳板救一下?说干就干。

1、先找出瓶颈到底在哪

凭感觉拍脑袋不行,得测。我把整条链路拆成几段,一段一段 curl + scp 跑数据。

测试方法

  • curl 测到 Cloudflare edge 的下载速度(https://speed.cloudflare.com/__down?bytes=52428800
  • scp 传一个 50MB 的随机文件测点对点吞吐

跑下来结果是这样的:

链路速度备注
本地 → CF edge10.9 MB/s客户端到 CDN 一点不慢
mini → CF edge5.0 MB/s家宽出口也还行
本地 → JP(直连)3.2 MB/sJP 小鸡带宽
JP → CF edge136 MB/s数据中心内网级别
本地 → CF Tunnel → mini0.48 MB/s❌ 瓶颈元凶

看到这里我人都傻了。两端到 CF edge 明明都有几 MB/s 起步,tunnel 一接起来怎么掉到 0.48 MB/s?

翻了一下资料才反应过来:CF Tunnel 是 mini 的 cloudflared 反向出站连一个 POP,客户端连另一个 POP,两个 POP 可能绕了大半个地球。你以为走的是"最快路径",实际上 Cloudflare 给你安排的是"他家机房空闲路径"。

2、第一个方案(失败了):在 JP 也装 cloudflared

顺着这个思路,我想既然本地连 CF edge 可能被扔到了奇怪的 POP,那让 JP 当跳板、JP 上装 cloudflared 是不是就能强制走东京 POP?

干就完事了:

bash
ssh jp 'curl -sL -o /usr/local/bin/cloudflared \ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \ && chmod +x /usr/local/bin/cloudflared'

然后从 JP 走 cloudflared + SSH 到 mini,scp 同一个 50MB 文件:

50 MB,95 秒,0.54 MB/s

对不起,贵不是苹果的错,是我的错,但这次是 Cloudflare 的错。

换 POP 屁用没有。瓶颈在 CF Tunnel 协议本身(QUIC over 反向连接的奇怪路由),换入口完全无效。

方案 A 宣告死亡。

3、反向 SSH 隧道登场(快 5 倍)

方案 A 测试的时候我顺手在 mini 上 ping 了一下 JP:

bash
ssh mini-cf 'ping -c 5 203.0.113.42' → round-trip min/avg/max = 0.337/0.416/0.477 ms

0.4ms!!! 国内到日本的 IP 不可能是这个延迟,除非是本地环回。

想了两秒反应过来:mini 上我本来就挂了一条到 JP 的科学上网链路,所以 mini 访问 JP IP 是走这条加速链路过去的,几乎等于和 JP 同机房。

这就好办了。如果 mini 直连 JP 这么快,我就让 mini 主动连出去到 JP,建一个反向 SSH 隧道,客户端跳板 JP 就能连回 mini。完全绕开 CF Tunnel。

实测 mini → JP 的吞吐:

scp /tmp/testfile_50m mini → jp → 50 MB,10.8 秒,4.6 MB/s

妥了,这就是可用速度。

架构长这样:

sequenceDiagram
    autonumber
    participant C as 客户端
    participant J as JP 跳板
    participant M as Mac mini

    Note over M,J: 阶段 1:autossh 常驻
    M->>J: ssh -R 2222:localhost:22 tunnel@jp
    J-->>M: 认证通过
    Note over J: 127.0.0.1:2222 监听

    Note over C,M: 阶段 2:客户端按需连
    C->>J: ssh jp (ProxyJump)
    C->>J: 请求转发到 localhost:2222
    J->>M: 经反向隧道推回 mini
    M-->>C: 端到端 SSH 握手

几个关键点:

  • mini 主动发起:不需要家里开端口映射,不需要公网 IP
  • 两层 SSH 各自认证:JP 看不到你和 mini 之间的明文,端到端加密
  • JP 只监听回环GatewayPorts no 保证反向端口外网访问不到

4、落地步骤(照抄能跑版)

4.1 JP 端:创建受限 tunnel 用户

不能给 mini 的反向隧道用 root,权限太大。单独开一个 tunnel 用户,用 authorized_keys 限制只能做端口转发:

bash
ssh jp ' useradd -m -s /bin/bash tunnel mkdir -p /home/tunnel/.ssh chown tunnel:tunnel /home/tunnel/.ssh chmod 700 /home/tunnel/.ssh '

4.2 mini 端:生成专用 key

bash
ssh mini 'ssh-keygen -t ed25519 -N "" \ -C "mini-reverse-tunnel-to-jp" \ -f ~/.ssh/id_tunnel_jp'

4.3 JP 端:写入公钥,加三重限制

这里是整个方案最关键的一行,少一个参数就裸奔:

bash
MINI_PUBKEY="$(ssh mini 'cat ~/.ssh/id_tunnel_jp.pub')" ssh jp "echo 'restrict,port-forwarding,command=\"echo tunnel-only; sleep infinity\" ${MINI_PUBKEY}' \ > /home/tunnel/.ssh/authorized_keys \ && chmod 600 /home/tunnel/.ssh/authorized_keys \ && chown tunnel:tunnel /home/tunnel/.ssh/authorized_keys"

参数解读:

  • restrict:一键关掉所有能力(pty、X11、agent forwarding、命令执行)
  • port-forwarding:把端口转发单独开回来(因为 restrict 默认连这个也禁了)
  • command="... sleep infinity":即使 key 泄露也拿不到 shell

4.4 mini 端:autossh + launchd 自愈守护

ssh -R 直接跑不行,断线就没了。要上 autossh(一个会自动重连 SSH 的工具):

bash
ssh mini '/opt/homebrew/bin/brew install autossh' ssh mini 'ssh-keyscan -t ed25519 203.0.113.42 > ~/.ssh/known_hosts_tunnel'

然后写 launchd plist(macOS 的开机自启服务配置),开机自启、挂了自动拉起来:

~/Library/LaunchAgents/com.example.reverse-tunnel-jp.plist

xml
<?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>Label</key><string>com.example.reverse-tunnel-jp</string> <key>ProgramArguments</key> <array> <string>/opt/homebrew/bin/autossh</string> <string>-M</string><string>0</string> <string>-N</string><string>-T</string> <string>-o</string><string>ServerAliveInterval=30</string> <string>-o</string><string>ServerAliveCountMax=3</string> <string>-o</string><string>ExitOnForwardFailure=yes</string> <string>-o</string><string>StrictHostKeyChecking=yes</string> <string>-o</string><string>UserKnownHostsFile=/Users/你的用户名/.ssh/known_hosts_tunnel</string> <string>-i</string><string>/Users/你的用户名/.ssh/id_tunnel_jp</string> <string>-R</string><string>2222:localhost:22</string> <string>[email protected]</string> </array> <key>RunAtLoad</key><true/> <key>KeepAlive</key><true/> <key>ThrottleInterval</key><integer>10</integer> </dict> </plist>

加载:

bash
launchctl load ~/Library/LaunchAgents/com.example.reverse-tunnel-jp.plist

验证自愈:pkill -9 autossh,等 10 秒再看,launchd 会自动把它拉起来。

4.5 客户端:一段 SSH config 搞定

Host mini-jp HostName localhost Port 2222 User 你的mini用户名 ProxyJump jp IdentityFile ~/.ssh/id_ed25519 StrictHostKeyChecking no UserKnownHostsFile /dev/null LocalForward 55900 localhost:5900

之后就能:

bash
ssh mini-jp

搞定。

5、踩坑实录(不看白不看)

坑 1:restrict 默认连 port-forwarding 也禁了

一开始我只写了 restrict,结果反向隧道死活建不起来,报 administratively prohibited。翻 man 手册才知道 restrict 的"所有权限"连端口转发也一起关了,要显式加 port-forwarding 开回来。

坑 2:SSH config Host 行前不能缩进

我复制粘贴的时候不小心 Host mini-jp 前面带了 2 个空格,SSH 倒是容忍解析了,但有些选项(像 LocalForward)会不生效,debug 半天才发现。

不良示范:

# 反向 SSH 隧道 Host mini-jp HostName localhost

好的示范:

# 反向 SSH 隧道 Host mini-jp HostName localhost

Host 顶格写,选项 4 空格缩进,别搞花的。

坑 3:VNC LocalForward 不生效

配好 LocalForward 55900 localhost:5900 之后,满怀期待打开"屏幕共享"连 vnc://localhost:55900,告诉我连接失败

我当时脑子一热跑了一句:

bash
ssh mini-jp 'lsof -iTCP:5900 -sTCP:LISTEN'

确认 mini 上 5900 是开的。但 VNC 就是连不上。

问题出在:ssh mini-jp '命令' 是一次性会话,命令跑完 session 就断,LocalForward 的端口也跟着关了。

VNC 需要 SSH 隧道保持。正确姿势:

bash
ssh -fN mini-jp
  • -f:后台
  • -N:不执行远程命令

然后 VNC 连 vnc://localhost:55900,丝滑。

想杀掉后台隧道:

bash
pkill -f 'ssh.*mini-jp'

或者精准一点:

bash
lsof -iTCP:55900 -sTCP:LISTEN # 找 PID kill <PID>

注意-f 后台模式关闭终端窗口不会自动停隧道,要手动杀。想"关窗口即断"用 ssh -N mini-jp(前台阻塞,Ctrl+C 结束)。

6、最终效果

端到端测试:

ssh mini-jp 'echo ok' → 3.4s 建连 scp mini-jp:/tmp/testfile_50m . → 50 MB,20.7 秒,2.4 MB/s

对比 CF Tunnel 的 0.48 MB/s,快了整整 5 倍。 日常 VNC 屏幕共享也完全能用,画质自适应之后跟本地没啥差别。

自愈也验证过了,断网、重启、kill 进程,autossh + launchd 都能在 10 秒内恢复。

7、日常怎么用

家里三种连接方式按场景切换:

bash
ssh mini # 局域网直连,最快 ssh mini-jp # 主力,远程走反向 SSH,~2.4 MB/s ssh mini-cf # 兜底,CF Tunnel(JP 挂了用它)

VNC 屏幕共享:

bash
ssh -fN mini-jp # 后台建隧道 open vnc://localhost:55900 # 连接 # 用完:pkill -f 'ssh.*mini-jp'

8、需要准备的软件 / 服务 / 账号清单

类型名字用途花费
VPS任意海外小鸡(日本优先)跳板 + 反向隧道落地约 ¥20~50/月
加速mini 上到 JP 的加速链路让 mini 到 JP 速度快已有
账号Cloudflare兜底 CF Tunnel 方案免费
软件cloudflaredCF Tunnel 客户端免费
软件autossh守护反向 SSHbrew 免费
软件OpenSSHSSH 本身,现代 Mac/Linux 自带免费
内置macOS launchd开机自启 + 自愈免费

总结

整条方案里最关键的不是技术,是先把瓶颈量化出来。我一开始也以为"加个 JP 跳板 cloudflared 就能解决",结果方案 A 白做一轮。是分段 scp 测速逼出了"CF Tunnel 协议本身就慢"这个结论,才有后面方案 C 的顺利落地。

优点

  • 快 5 倍,ping 延迟也更稳
  • 绕开 CF Tunnel 的玄学路由
  • 复用 mini 已有的加速出口,天然契合
  • autossh + launchd 真能做到开机即用

缺点

  • 依赖 mini 上的 JP 加速链路,链路挂了隧道也挂
  • 单点:JP 小鸡一关就连不上(所以 CF Tunnel 保留做兜底,真·"先完成再完美")
  • 上行吞吐受限于 mini → JP 带宽

能跑通了再说,先干出来比啥都强。如果你也在家 selfhost 一台 Mac mini / NAS,下次 CF Tunnel 卡成 PPT 的时候,记得还有这条野路子。


加入AI技术交流群,可以先关注本公众号,然后在后台回复「交流」,获取入群方式。

感谢你看到这里,如果觉得有帮助,转发,点个赞或在看,就是对我最大的鼓励。也欢迎留言交流你的想法~

本文作者:花菜

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!