使用 cloudflared 穿透内网实现 SSH 免密连接
使用 cloudflared 穿透内网实现 SSH 免密连接
在日常开发和运维中,我们经常需要 SSH 连接到内网服务器。如果服务器没有公网 IP,或者隐藏在防火墙 / NAT 后面,传统的直连方式就行不通了。借助 Cloudflare Tunnel(cloudflared)+ Cloudflare Access,可以安全、稳定地实现 SSH 内网穿透,并配合 SSH config 做到”一键连接”。
工作原理
整个链路分为两段:
- 服务端 → Cloudflare 边缘节点:内网服务器运行
cloudflared tunnel,与 Cloudflare 边缘建立持久化的 HTTP/2 隧道(国内用户建议阅读 Cloudflare Tunnel HTTP/2 优化指南)。 - 客户端 → Cloudflare 边缘节点:本地使用
cloudflared access tcp作为 SSH 的ProxyCommand,由 cloudflared 负责与 Cloudflare 边缘建立连接并转发 TCP 流量。
1 | |
安装 cloudflared
Linux (Debian/Ubuntu):
1 | |
手动下载(通用):
1 | |
登录 Cloudflare Access
在客户端侧,需要先通过 cloudflared login 获取认证凭据。如果你的 Tunnel 绑定了 Cloudflare Access 策略(如一次性 PIN 验证),首次使用时会自动打开浏览器提示登录:
1 | |
浏览器会自动打开,选择对应的团队域名完成授权即可。授权成功后凭据会保存到 ~/.cloudflared/ 目录,后续连接无需再次登录。
配置 SSH ProxyCommand
在 ~/.ssh/config 中添加如下配置:
1 | |
配置说明:
| 字段 | 含义 |
|---|---|
Host |
别名,用于 ssh <别名> 直接连接 |
HostName |
实际主机名,必须与 Cloudflare Access 中配置的公开主机名一致 |
User |
SSH 登录用户名 |
IdentityFile |
指定私钥文件路径 |
IdentitiesOnly yes |
仅使用指定的私钥,不尝试其他密钥(避免 Too many authentication failures) |
ProxyCommand |
将 SSH 流量通过 cloudflared 转发,%h 自动替换为 HostName |
ServerAliveInterval 10 |
每 10 秒发送一个心跳包,防止长连接被防火墙或 NAT 设备断开 |
ServerAliveCountMax 3 |
连续 3 次心跳无响应才判定断线,避免网络抖动导致误断 |
Ciphers |
指定加密算法,chacha20-poly1305 比默认 AES 更省 CPU,适合低配机器 |
IPQoS throughput |
标记 SSH 流量为高吞吐类型,减少路由设备的 QoS 限速 |
关键点:
ProxyCommand中的--hostname %h,%h是 SSH 的内置变量,会自动展开为HostName字段的值。这样当你修改HostName时,无需同步修改ProxyCommand。- cloudflared 二进制路径建议使用绝对路径,避免
PATH查找问题。 IdentitiesOnly yes很重要——如果本地有多个 SSH 密钥,不开启此选项可能会一次性尝试所有密钥,导致目标服务器返回Too many authentication failures。ServerAliveInterval+ServerAliveCountMax组合确保连接稳定性,尤其适合经过 cloudflared 隧道这种中间有 NAT/防火墙的场景。Ciphers chacha20-poly1305在无 AES-NI 指令集的 CPU(如某些 ARM 或老旧 x86)上性能远优于默认的 AES-GCM,能显著降低加密开销。IPQoS throughput告诉底层网络将此连接的 DSCP 标记为高吞吐类型,部分路由器会给予更高的带宽优先级。
验证连接
配置完成后,直接使用 ssh 别名即可连接:
1 | |
测试连通性(详细输出):
1 | |
正常情况下,你会看到类似输出:
1 | |
使用场景
场景一:Git 免密克隆 / 推送
内网部署了 Gitea / GitLab / GitBucket 等代码托管平台,通过 cloudflared 暴露 SSH 端口后,可以像使用 GitHub 一样正常操作:
1 | |
由于 SSH config 中已配置了 IdentityFile 和 IdentitiesOnly,整个过程无需输入密码。
场景二:管理多台内网服务器
如果有多个内网 SSH 服务暴露在同一个域名下不同端口,或者不同的子域名:
1 | |
场景三:SCP / SFTP 文件传输
连接方式与 SSH 完全一致:
1 | |
常见问题
Q: 连接时报 failed to dial to tunnel 错误
首先检查 cloudflared access login 是否已完成认证。如果凭据过期,重新执行登录即可。
Q: 连接超时卡住不动
国内网络环境下,可能与 QUIC 协议被运营商干扰有关。可以参考 Cloudflare Tunnel HTTP/2 优化指南 将协议强制切换为 HTTP/2。
Q: Permission denied (publickey) 认证失败
- 确认
IdentityFile路径正确且私钥文件权限为600 - 确认公钥已添加到目标服务器的
~/.ssh/authorized_keys - 尝试用
ssh -v查看详细的认证流程
Q: Too many authentication failures 错误
确保配置中设置了 IdentitiesOnly yes,它会让 SSH 客户端只使用你指定的那一个密钥,而不会把所有本地密钥都试一遍。
Q: 如何让 cloudflared 在后台持续运行?
对于客户端侧,cloudflared access tcp 是每次 SSH 连接时由 ProxyCommand 临时启动的,无需后台常驻。服务端则需要将 cloudflared tunnel 配置为 systemd 服务:
1 | |
小结
cloudflared access tcp + SSH ProxyCommand 的组合,让我们可以在不暴露公网端口、不修改防火墙规则的前提下,安全地访问内网 SSH 服务。配合 SSH config 的别名机制,使用体验与直连几乎无差异。无论是 Git 免密推送、远程服务器管理还是文件传输,这套方案都能胜任。