搜 索

💻远程访问 Mac 从入门到放弃

  • 12阅读
  • 2026年04月25日
  • 0评论
首页 / 编程 / 正文

前言

最近我入手了一台 Mac mini,打算把它当作家里常驻的远程服务器:跑些长时间任务、做开发环境、当 NAS、随时从公司或外面那台 MacBook 连回来用。要实现这件事,核心问题是怎么把家里这台机器安全地暴露到公网。

传统方案有几个坑:

  • 路由器端口转发:得有公网 IP,运营商现在大多 NAT,搞不定;就算能搞还要应付动态 IP、DDoS、扫描器。
  • frp / ngrok:需要自己一台带公网的 VPS 当中继,多一份成本和维护。
  • Tailscale / ZeroTier:好用,但每台想接入的设备都得装客户端。
  • Cloudflare Tunnel:免费,不要公网 IP,自带 HTTPS 和 DDoS 防护,可以叠加 Zero Trust Access 做身份验证。本文采用这套方案。

最终目标:在外面的 MacBook 上,一条 ssh user@ssh.example.com 就能连回家里的 Mac mini,并且能随时拉起屏幕共享(VNC)。

整体架构

flowchart LR Client["客户端 Mac
(MacBook)"] Edge["Cloudflare 边缘节点
(443 端口)"] Tunnel["Cloudflared
(运行在 Mac mini)"] Host["被访问 Mac
(Mac mini)"] Client -- "ssh / vnc
经 cloudflared access" --> Edge Edge -- "加密隧道
主动出站" --> Tunnel Tunnel -- "localhost:22
localhost:5900" --> Host

关键点:Mac mini 主动连出去到 Cloudflare 边缘建立长连接,所以根本不需要公网 IP,也不用在路由器上开任何端口。

前置条件

  1. 一个已托管在 Cloudflare 的域名(DNS 的 NS 已切到 Cloudflare)。
  2. 被访问的 Mac mini,开启了「远程登录(SSH)」和「屏幕共享(VNC)」。
  3. 一台客户端 Mac,安装了 Homebrew。

总体流程

flowchart TD A[安装 cloudflared
host 端] --> B[cloudflared tunnel login] B --> C[创建 Tunnel
cloudflared tunnel create] C --> D[编辑 config.yml
定义 ingress 规则] D --> E[路由 DNS
tunnel route dns] E --> F[前台测试
tunnel run] F --> G{是否正常?} G -- 是 --> H[安装为 service
service install] G -- 否 --> I[排查日志] I --> D H --> J[客户端配置
~/.ssh/config] J --> K[ssh / vnc 连接]

一、Host 端配置(Mac mini)

1. 安装 cloudflared

brew install cloudflared

2. 登录并授权

cloudflared tunnel login

会自动打开浏览器,登录后选择要使用的域名进行授权。完成后会在 ~/.cloudflared/ 生成 cert.pem

3. 创建隧道

cloudflared tunnel create macminivisit

记下返回的 Tunnel UUID,并确认 ~/.cloudflared/<UUID>.json 凭证文件已生成。

4. 编辑 config.yml

新建 ~/.cloudflared/config.yml

tunnel: 3ff7b7df-25c3-43d9-a49f-f9bc05701b9c
credentials-file: /Users/你的用户名/.cloudflared/3ff7b7df-25c3-43d9-a49f-f9bc05701b9c.json

ingress:
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - hostname: vnc.example.com
    service: tcp://localhost:5900
  - service: http_status:404

几个容易踩的点:

  • credentials-file 必须是绝对路径,不能写 ~
  • VNC 的 service 是 tcp://,不是 vnc://
  • 最后必须有兜底 http_status:404,否则会启动失败。
  • hostname 用一级子域名ssh.example.com),不要用深层子域名(ssh.home.example.com),否则会撞 SSL 证书覆盖问题。

校验配置:

cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://ssh.example.com

5. 在 Cloudflare 创建 DNS 路由

cloudflared tunnel route dns macminivisit ssh.example.com
cloudflared tunnel route dns macminivisit vnc.example.com

去 Dashboard → DNS 确认两条记录都是:

  • 类型:CNAME
  • 内容:<UUID>.cfargotunnel.com
  • 状态:橙色云(Proxied)

如果命令报记录冲突,去 Dashboard 手动删掉旧的 A/CNAME 再跑。

6. 前台测试

不要直接装 service,先在终端里跑:

cloudflared tunnel run macminivisit

看到 4 条 Registered tunnel connection 就说明边缘连上了。这时候可以另开一个终端测试访问。

7. 安装为系统服务

确认前台能跑后:

sudo cloudflared service install
sudo launchctl start com.cloudflare.cloudflared

确认连接数:

cloudflared tunnel info macminivisit
# 应该看到 4x connections
重要陷阱service install复制 config.yml/Library/Application Support/com.cloudflare.cloudflared/,service 用的是这个副本。以后改配置要么再 uninstall + install 一次,要么直接改系统目录里那个文件,再重启服务。
sudo cp ~/.cloudflared/config.yml "/Library/Application Support/com.cloudflare.cloudflared/config.yml"
sudo launchctl stop com.cloudflare.cloudflared
sudo launchctl start com.cloudflare.cloudflared

8. 打开 macOS 共享开关

系统设置 → 通用 → 共享:

  • 远程登录 → 打开(SSH,端口 22)
  • 屏幕共享 → 打开(VNC,端口 5900)

并确认下方「允许以下用户访问」包含你要用的账号。

二、SSH 客户端配置

不能直接用裸 ssh 连——Cloudflare 边缘只开 80/443,22 端口根本不通,会超时。必须用 cloudflared access ssh 把流量包成 HTTPS 走 443。

1. 装 cloudflared

brew install cloudflared
which cloudflared
# Apple Silicon: /opt/homebrew/bin/cloudflared
# Intel:        /usr/local/bin/cloudflared

2. 编辑 ~/.ssh/config

Host ssh.example.com
    ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
    User 你的Mac用户名
chmod 600 ~/.ssh/config

3. 验证 ProxyCommand 已生效

ssh -G ssh.example.com | grep -i proxycommand

应该看到:

proxycommand /opt/homebrew/bin/cloudflared access ssh --hostname ssh.example.com

如果什么都没输出,就是 Host 没匹配上,检查拼写或前面是否有 Host * 覆盖。

4. 连接

ssh user@ssh.example.com

第一次连接会弹浏览器走 Cloudflare 验证(如果配了 Access 策略),之后就跟普通 SSH 一样。

三、VNC 屏幕共享配置

1. 客户端起一个本地端口转发

cloudflared access tcp --hostname vnc.example.com --url localhost:5900

这条命令会一直挂在前台,保持窗口开着。如果本地 5900 已被占用(比如自己 Mac 也开了屏幕共享),换个端口比如 5901。

2. 用 Finder 连接

⌘K → 「连接服务器」→ 输入:

vnc://localhost:5900

输入 Mac mini 的用户名/密码,桌面就出来了。

3.(可选)做成开机自启

在客户端 Mac 上新建 ~/Library/LaunchAgents/com.user.cloudflared-vnc.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.user.cloudflared-vnc</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/cloudflared</string>
        <string>access</string>
        <string>tcp</string>
        <string>--hostname</string>
        <string>vnc.example.com</string>
        <string>--url</string>
        <string>localhost:5900</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>

加载:

launchctl load ~/Library/LaunchAgents/com.user.cloudflared-vnc.plist

之后只要客户端 Mac 开机,本地 5900 就一直在转发,直接 vnc://localhost 就能连。

四、踩坑实录与排查

这部分是最有价值的——我配置过程中几乎每个错误都遇到了一遍。

flowchart TD Err{遇到错误} Err --> A[TLS handshake failure] Err --> B[Operation timed out] Err --> C[websocket: bad handshake] Err --> D[no active connection] Err --> E[此主机名未被证书覆盖] A --> A1[证书未覆盖深层子域名
→ 改一级子域名
或开启 Total TLS] B --> B1[ProxyCommand 未生效
→ 检查 ~/.ssh/config] C --> C1[ingress hostname 不匹配
→ 改 config.yml + 重启] D --> D1[cloudflared 未运行
→ 前台 run 看错误] E --> E1[Universal SSL 仅覆盖
根域名和一级子域名]

错误 1:此主机名未被证书覆盖

原因:Cloudflare 的免费 Universal SSL 默认只覆盖根域名和一级子域名(example.com*.example.com),不覆盖 ssh.home.example.com 这种二级子域名。

解决

  • 首选:把 hostname 改成一级子域名(ssh.example.com),免费立即可用。
  • 次选:启用 Total TLS(SSL/TLS → Edge Certificates → Total TLS),免费给所有子域名签发证书,CA 选 Let's Encrypt 即可。
  • 不要:上传自己 Let's Encrypt 证书的 Custom Certificates 功能仅限 Business / Enterprise 套餐(每月 $200+)。

错误 2:remote error: tls: handshake failure

典型场景:在客户端跑 cloudflared access ssh --hostname ssh.example.com 直接报错。

原因排查清单

  1. 真的把 example.com 替换成自己的域名了吗?(占位符不算)
  2. DNS 记录是 CNAME 指向 <UUID>.cfargotunnel.com、橙色云吗?
  3. 证书是否覆盖该子域名?(见错误 1)
  4. SSL/TLS 模式是否设为 Full?(不是 Off / Flexible)

curl -v https://ssh.example.com 直接验证:

  • error 1033 → 隧道没运行
  • HTTP 404 + cf-ray 头 → 边缘 OK,但 ingress 没匹配
  • SSL 错误 → 证书问题

错误 3:websocket: bad handshake

原因:边缘找到了隧道,但隧道里的 ingress 没匹配上当前 hostname,所以返回 404,cloudflared access 拿到非 101 响应升级 websocket 失败。

最常见场景:换了域名后只更新了 DNS,没改 host 端的 config.yml,或者改了 ~/.cloudflared/config.yml 但 service 用的是 /Library/Application Support/com.cloudflare.cloudflared/config.yml 那个副本。

解决

# 同步配置
sudo cp ~/.cloudflared/config.yml "/Library/Application Support/com.cloudflare.cloudflared/config.yml"

# 重启服务
sudo launchctl stop com.cloudflare.cloudflared
sudo launchctl start com.cloudflare.cloudflared

# 验证 ingress 匹配
cloudflared tunnel ingress rule https://ssh.example.com

错误 4:Your tunnel ... does not have any active connection

原因:cloudflared 进程根本没跑起来,或跑起来后连不上 Cloudflare 边缘。

排查:前台跑一次 cloudflared tunnel run macminivisit 看真实错误:

错误解决
Cannot determine default configuration path--config ~/.cloudflared/config.yml
error parsing YAMLconfig.yml 缩进错了,重新检查
Couldn't read credentials filecredentials-file 路径不对,必须绝对路径
Unable to reach the origin service远程登录/屏幕共享没开
网络超时 / failed to connect to edge公司网封 UDP,加 protocol: http2

如果是网络问题,在 config.yml 顶部加:

protocol: http2

错误 5:Operation timed out(裸 ssh 连接)

原因:客户端 SSH 直接去连 Cloudflare 边缘的 22 端口,但边缘只开 80/443,肯定超时。

解决:必须配 ~/.ssh/config 用 ProxyCommand。验证:

ssh -G ssh.example.com | grep proxycommand

无输出 → Host 没匹配上,检查拼写。

五、安全加固:Cloudflare Access

把 SSH/VNC 直接挂公网,即使有强密码也是定时炸弹。强烈建议加一层 Cloudflare Access(Zero Trust 免费版每月 50 用户额度,个人用绰绰有余)。

操作:

  1. 进入 Cloudflare Zero Trust 控制台。
  2. Access → Applications → Add application → Self-hosted
  3. Application domain 填 ssh.example.com
  4. 添加策略:Action = Allow,规则 = Emails,填你的邮箱。
  5. vnc.example.com 重复一次(VNC 协议本身较弱,更需要这层保护)。

之后第一次连接会弹浏览器要求邮箱验证,token 缓存到本地后续无感。即使有人扫到域名,没邮箱权限也碰不到 22/5900。

flowchart LR A[访问者] --> B{Cloudflare Access
身份验证} B -- "通过" --> C[隧道] B -- "拒绝" --> D[拦截在边缘] C --> E[Mac mini
SSH/VNC]

六、常用命令速查

# Host 端
cloudflared tunnel list                    # 列出所有隧道
cloudflared tunnel info macminivisit       # 看连接数
cloudflared tunnel ingress validate        # 验证 ingress 配置
cloudflared tunnel ingress rule https://x  # 测试 hostname 匹配哪条规则

# Service 管理
sudo cloudflared service install
sudo cloudflared service uninstall
sudo launchctl start  com.cloudflare.cloudflared
sudo launchctl stop   com.cloudflare.cloudflared
sudo log stream --predicate 'process == "cloudflared"' --info

# 客户端
cloudflared access ssh --hostname ssh.example.com           # 测试 SSH 隧道
cloudflared access tcp --hostname vnc.example.com --url localhost:5900  # 端口转发
cloudflared access login https://ssh.example.com            # 手动认证

# SSH 调试
ssh -G ssh.example.com | grep -i proxy     # 验证 ProxyCommand
ssh -vvv user@ssh.example.com              # 看完整流程

七、为什么标题叫"从入门到放弃"

入门容易,放弃也容易:装个 cloudflared、登录、创建 tunnel,看起来三五条命令的事。

放弃不是因为它不好用——它非常好用——而是因为中间有一连串隐蔽的坑:service 用的是另一份 config 副本、Universal SSL 只覆盖一级子域名、Cloudflare 边缘不开 22 端口、ProxyCommand 必须用绝对路径……每一个错误信息都长得不像它的真实原因,不踩一遍很难定位。

但只要把这些坑踩平了,整套方案就成了真正的"配一次用一辈子":Mac mini 7×24 在家里跑着,从地球任何角落一条 SSH 连回去,从未中断。

值得一坑。


附:完整配置清单备份

Host 端 ~/.cloudflared/config.yml

tunnel: <你的UUID>
credentials-file: /Users/<用户名>/.cloudflared/<UUID>.json
protocol: http2  # 网络封 UDP 时打开

ingress:
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - hostname: vnc.example.com
    service: tcp://localhost:5900
  - service: http_status:404

客户端 ~/.ssh/config

Host ssh.example.com
    ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
    User <Mac用户名>

客户端 VNC LaunchAgent:见正文第三部分第 3 节。

评论区
暂无评论
avatar