Qwen Agent + Cloudflare Tunnel:无公网 IP/端口容器 SSH 远程访问完整教程
本文记录一次真实可用的 Qwen Agent 实践:Qwen 提供的 Linux Agent/容器环境可以执行命令并主动访问公网,但没有公网 IP,也没有给用户开放 SSH 端口。目标是从自己的 Windows 11 电脑直接 SSH 进入这个 Agent 容器。
为了避免把真实入口长期暴露在公开文章中,下面统一使用 qwen.example.com 作为示例域名;实际使用时替换为你在 Cloudflare 中配置的真实子域名即可。这套方法并不只适用于 Qwen,只要其他 Agent、沙箱或临时容器允许主动出网并运行 sshd 与 cloudflared,同样可以复用。
一、最终实现效果
最终链路如下:
Windows 11
│
│ OpenSSH + cloudflared ProxyCommand
▼
Cloudflare Edge
│
│ Cloudflare Tunnel
▼
厂商 Agent / Linux 容器
│
├── cloudflared(主动出站连接 Cloudflare)
│
└── sshd → 127.0.0.1:2222
整个过程中,Agent 一侧不需要开放任何公网入站端口。只要容器能够主动访问公网,并允许运行 cloudflared 和 sshd,就可以把本地 SSH 服务通过 Cloudflare Tunnel 暴露出来。
本次实际测试环境:
- Agent:Linux x86_64 容器,Debian 12 系环境
- 权限:
root - SSH Server:OpenSSH 9.2p1
- SSH 内部监听:
127.0.0.1:2222 - cloudflared:2026.7.3
- 客户端:Windows 11 + OpenSSH for Windows
- Cloudflare Tunnel:Dashboard 创建的 remotely-managed tunnel
二、为什么 Cloudflare Tunnel 能解决这个问题
传统 SSH 需要客户端直接访问服务器的公网地址和 SSH 端口,例如:
ssh → 公网IP:22 → sshd
但厂商 Agent/沙箱通常处于 NAT、容器网络或内部集群中,没有公网 IP,也没有开放入站端口,因此外部无法直接连接。
Cloudflare Tunnel 的思路正好相反:
Agent 内的 cloudflared
│
└────主动出站────► Cloudflare
外部客户端连接 Cloudflare 后,Cloudflare 再通过这条已经建立好的 Tunnel,把流量送到 Agent 内部的本地服务。
因此它不要求:
- Agent 拥有公网 IP
- 厂商开放 TCP/22
- 配置路由器端口映射
- 自己购买 VPS 做 FRP 中转
但仍然要求 Agent 至少能够主动访问公网,并且厂商没有禁止此类 Tunnel/后台进程。
三、开始前先检查 Agent 环境
先让 Agent 执行:
whoami
uname -a
command -v curl || true
command -v sshd || true
command -v cloudflared || true
cat /etc/os-release
理想条件是:
root # 最方便,但普通用户也有办法
Linux ... x86_64 GNU/Linux # 常见容器架构
/usr/bin/curl # 能下载文件
如果 sshd 和 cloudflared 不存在没有关系,后面安装即可。
还应测试基本公网访问:
curl -I --max-time 10 https://www.cloudflare.com
注意:HTTPS 能访问只说明 TCP/443 可用,并不能证明 Cloudflare Tunnel 一定能建链。当前 Cloudflare Tunnel 主要要求容器能够访问 Cloudflare Tunnel 边缘的 TCP/UDP 7844:UDP 用于 QUIC,TCP 用于 HTTP/2;cloudflared 默认会自动选择并在必要时回退。管理 API 与更新还会使用 TCP/443。
从 cloudflared 2026.5.2 开始,cloudflared tunnel run 与 cloudflared tunnel diag 会自动做连通性预检,包括:
- DNS:
region1.v2.argotunnel.com、region2.v2.argotunnel.com - UDP/7844:QUIC
- TCP/7844:HTTP/2
- TCP/443:Cloudflare 管理 API
本次实际环境使用 cloudflared 2026.7.3,已经包含这套预检逻辑。因此如果后面 Tunnel 启动失败,优先直接运行 cloudflared tunnel diag,比只用 curl https://www.cloudflare.com 更有判断价值。
四、在 Windows 生成一把 Agent 专用 SSH 密钥
不要把长期用于其他服务器的老私钥到处复用。建议给这个 Agent 单独生成一把 Ed25519 密钥。
Windows PowerShell:
ssh-keygen -t ed25519 -f "$env:USERPROFILE\.ssh\id_ed25519_qwen" -C "qwen-agent"
生成:
C:\Users\你的用户名\.ssh\id_ed25519_qwen # 私钥,绝不能上传
C:\Users\你的用户名\.ssh\id_ed25519_qwen.pub # 公钥,可以给远端
查看公钥:
Get-Content "$env:USERPROFILE\.ssh\id_ed25519_qwen.pub"
输出类似:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... qwen-agent
后面只需要把这一整行公钥放入 Agent,私钥始终保留在 Windows。
五、在 Agent 安装 OpenSSH Server
Debian/Ubuntu:
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server
确认:
command -v sshd
/usr/sbin/sshd -V 2>&1 | head -1 || true
如果看到 /usr/sbin/sshd,说明安装完成。
生成服务器 Host Key:
ssh-keygen -A
这里的 Host Key 是服务器身份密钥,与 Windows 上用于登录的用户密钥不是同一套东西。客户端第一次连接时,SSH 会把服务器 Host Key 指纹写入 known_hosts。
六、把 Windows 公钥加入 Agent
在 Agent 中执行:
mkdir -p /root/.ssh
chmod 700 /root/.ssh
然后把 Windows 的 id_ed25519_qwen.pub 整行加入:
cat >> /root/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... qwen-agent
EOF
chmod 600 /root/.ssh/authorized_keys
注意:这里必须是 .pub 公钥,绝不能把 Windows 私钥上传到 Agent。
如果担心重复添加,可以使用:
PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... qwen-agent'
grep -qxF "$PUB" /root/.ssh/authorized_keys || echo "$PUB" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
七、在容器里启动一个只监听 localhost 的 SSH Server
临时 Agent 容器通常没有 systemd,所以不要依赖 systemctl start ssh。这里直接启动独立的 sshd。
创建配置:
cat >/tmp/agent_sshd_config <<'EOF'
Port 2222
ListenAddress 127.0.0.1
HostKey /etc/ssh/ssh_host_ed25519_key
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
UsePAM no
PidFile /tmp/agent-sshd.pid
Subsystem sftp internal-sftp
EOF
检查语法:
/usr/sbin/sshd -t -f /tmp/agent_sshd_config
没有输出通常就是通过。然后后台启动:
nohup /usr/sbin/sshd -D -e -f /tmp/agent_sshd_config >/tmp/sshd.log 2>&1 &
只监听 127.0.0.1:2222 的好处是:即使容器网络中存在其他主机,也无法直接通过容器网卡访问 SSH;真正能访问它的是同一容器里的 cloudflared。
八、验证 sshd 是否真的正常
先看进程和日志:
ps aux | grep '[s]shd'
cat /tmp/sshd.log
再看监听端口:
ss -lntp 2>/dev/null | grep ':2222' || \
netstat -lntp 2>/dev/null | grep ':2222' || true
最推荐的 SSH Banner 测试:
timeout 3 ssh-keyscan -p 2222 127.0.0.1 2>&1 || true
正常会看到类似:
127.0.0.1:2222 SSH-2.0-OpenSSH_9.2p1 Debian-...
如果 Agent 内部有一把测试私钥,也可以直接本机测试:
ssh -i /path/to/test_private_key -p 2222 root@127.0.0.1 'whoami; hostname; pwd'
只要本地 SSH 能成功,再进入 Cloudflare 阶段;不要在 origin 本身还没通时去调 Tunnel。
九、安装 cloudflared
如果系统是 Linux x86_64,可以直接下载官方 AMD64 二进制:
curl -L --fail --retry 3 \
-o /usr/local/bin/cloudflared \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x /usr/local/bin/cloudflared
cloudflared --version
也可以按照 Cloudflare Dashboard 创建 Tunnel 时给出的 Debian 安装命令安装。
建议再执行:
cloudflared tunnel diag
它可以帮助检查 DNS、Cloudflare API 以及 Tunnel 传输所需网络是否可达。
如果 HTTPS 能上网但 Tunnel 一直无法注册连接,尤其要关注厂商是否封锁了 Cloudflare Tunnel 所使用的出站连接。
十、在 Cloudflare Dashboard 创建 remotely-managed Tunnel
登录 Cloudflare Dashboard。按 2026 年当前界面进入:
Networking → Tunnels → Create a tunnel
创建 remotely-managed Tunnel,选择 cloudflared Connector,并给 Tunnel 起一个名字,例如:
qwen-agent
系统会根据操作系统给出安装和运行命令。对于这种临时容器,通常**不要使用 service install**,因为容器里可能根本没有 systemd。
Dashboard 会给出类似:
cloudflared tunnel run --token eyJ...
这里的 eyJ... 是 Tunnel Token,它具有启动该 Tunnel Connector 的权限,必须当作敏感凭据保护。
关键区别:不要误走 tunnel login
Dashboard 创建的是 remotely-managed tunnel,运行 Connector 时只需要 Tunnel Token。
因此不要因为看不到 cert.pem 就执行:
cloudflared tunnel login
cloudflared tunnel create xxx
cloudflared tunnel route dns xxx ...
这些是 locally-managed tunnel 的另一套工作流。两套方式不要混用。
十一、用 Token 文件后台运行 Tunnel
为了避免 Token 直接长期出现在进程命令行中,可以把它保存为只允许 root 读取的文件:
mkdir -p /root/.cloudflared
chmod 700 /root/.cloudflared
cat >/root/.cloudflared/qwen-tunnel.token <<'EOF'
eyJ...这里粘贴 Dashboard 给出的完整 Tunnel Token...
EOF
chmod 600 /root/.cloudflared/qwen-tunnel.token
然后后台启动:
nohup cloudflared tunnel run \
--token-file /root/.cloudflared/qwen-tunnel.token \
>/tmp/cloudflared.log 2>&1 &
本文实测的 cloudflared 2026.7.3 已明确支持 --token-file;可以通过 cloudflared tunnel run --help 再确认当前版本是否包含该参数。--token 与 --token-file 同时存在时,--token 优先,因此不要同时配置两份来源。
检查:
sleep 3
pgrep -af cloudflared || true
tail -n 50 /tmp/cloudflared.log
如果日志出现 Registered tunnel connection,并且 Cloudflare Dashboard 显示 Connected/Healthy,说明 Tunnel Connector 已经成功建立。
十二、在 Tunnel 中添加 SSH Published Application
Tunnel Connected 后,在 Cloudflare Dashboard 进入当前 Tunnel。按当前界面依次选择:
Networking → Tunnels → qwen-agent → Routes → Add route → Published application
假设使用:
qwen.example.com
当前界面推荐填写:
Hostname: qwen.example.com
Service: SSH
URL: 127.0.0.1:2222
如果界面仍显示 Path 字段则留空。SSH 不是通过 /ssh 这种 HTTP Path 路由的。
Cloudflare 当前官方文档还明确建议:为这个公开 hostname 再创建一个 Access self-hosted application,通过身份策略限制谁可以访问。Published application 负责“把 SSH 服务接到 Cloudflare”,Access 负责“谁有资格到达这个入口”,远端 OpenSSH 公钥认证则继续作为 Linux 登录层。个人临时测试可以先把 SSH 链路跑通,但长期保留入口时建议把 Access 一并配置。
最终 origin 关系是:
qwen.example.com
↓
Cloudflare Tunnel
↓
ssh://127.0.0.1:2222
这里的 2222 是 Agent 内部端口,不是 Windows 后面要直接访问的公网端口。
十三、Windows 安装 cloudflared
Windows 11 可以直接使用 winget:
winget install --id Cloudflare.cloudflared
安装后重新打开 PowerShell,然后检查:
cloudflared --version
where.exe cloudflared
本次实际安装路径为:
C:\Program Files (x86)\cloudflared\cloudflared.exe
如果安装后当前终端暂时还找不到 cloudflared,可以直接在 SSH 配置中写这个绝对路径,不必等待 PATH 刷新。
十四、配置 Windows OpenSSH
编辑:
C:\Users\你的用户名\.ssh\config
加入:
Host qwen
HostName qwen.example.com
User root
IdentityFile C:/Users/你的用户名/.ssh/id_ed25519_qwen
IdentitiesOnly yes
ProxyCommand "C:/Program Files (x86)/cloudflared/cloudflared.exe" access ssh --hostname %h
如果 cloudflared 已正确加入 PATH,也可以把最后一行简化成:
ProxyCommand cloudflared access ssh --hostname %h
这里**不要写 Port 2222**。
原因是 2222 只存在于 Tunnel 后端:
Cloudflare Tunnel → Agent 的 127.0.0.1:2222
Windows 客户端访问的是 Cloudflare 上的 hostname,由 cloudflared access ssh 建立代理连接,而不是直接访问公网 :2222。
因此也不要使用:
ssh -p 2222 root@qwen.example.com
正确方式是:
ssh qwen
GUI SSH 客户端:不支持 ProxyCommand 时怎么连接
有些 GUI SSH 工具只允许填写“地址、端口、用户名、私钥”,没有 OpenSSH 的 ProxyCommand 配置项。这时不要把 qwen.example.com:2222 或 Agent 的内部端口直接填进去,而是让 Windows 上的 cloudflared 先建立一个本地 TCP 入口,再让 GUI SSH 客户端连接这个本地端口。
Cloudflare 官方的 client-side cloudflared 支持这种用法:
cloudflared access tcp --hostname qwen.example.com --url localhost:22222
这里的 22222 是 Windows 本机临时监听端口,可以换成其他未被占用的端口。这个命令需要保持运行;如果该 hostname 配置了 Cloudflare Access 策略,cloudflared 会按策略触发浏览器身份认证。
此时 GUI SSH 工具填写:
| 配置项 | 填写内容 |
|---|---|
| 名称 | qwen |
| 地址 / Host | 127.0.0.1 |
| 端口 | 22222 |
| 登录用户 | root |
| 验证方式 | 私钥 / Public Key / Key |
| 私钥 | C:\Users\你的用户名\.ssh\id_ed25519_qwen |
| 登录密码 | 留空 |
| 跳板机 | 不需要 |
| GUI 内代理 | 通常不需要 |
本教程前面已经将远端 sshd 配置为 PasswordAuthentication no,因此 GUI 客户端这里应选择私钥认证,不要再选“密码”。
这时完整链路变成:
GUI SSH 客户端
↓
127.0.0.1:22222
↓
Windows cloudflared access tcp
↓
qwen.example.com
↓
Cloudflare Tunnel
↓
Agent 127.0.0.1:2222
↓
sshd
注意两个端口不是一回事:
Windows 本地端口:22222 ← GUI SSH 工具连接这里
Agent 内部 SSH 端口:2222 ← Cloudflare Tunnel 后端连接这里
如果 GUI SSH 工具本身支持自定义 OpenSSH ProxyCommand,仍建议直接使用前面的:
ProxyCommand cloudflared access ssh --hostname %h
只有在 GUI 工具无法使用 ProxyCommand 时,才需要额外运行 cloudflared access tcp 作为本地桥接。Cloudflare 官方文档也明确说明,access tcp 可以监听任意本地端口,然后让客户端应用连接该端口。
如果 GUI 工具提示 Connection refused 127.0.0.1:22222,首先检查运行 cloudflared access tcp ... 的 PowerShell 窗口是否仍然存在,以及本地端口是否正在监听,而不是先去修改 Agent 的 SSH 配置。
十五、第一次连接与 Host Key
第一次连接建议使用详细日志:
ssh -v qwen
如果链路已经打通,会看到远端类似:
Remote protocol version 2.0, remote software version OpenSSH_9.2p1
第一次连接还可能出现:
The authenticity of host 'qwen.example.com' can't be established.
ED25519 key fingerprint is SHA256:...
Are you sure you want to continue connecting?
确认指纹无误后输入:
yes
SSH 会把服务器 Host Key 写入:
C:\Users\你的用户名\.ssh\known_hosts
随后进行用户公钥认证。如果 Windows 公钥已正确放入远端 /root/.ssh/authorized_keys,就会进入 Shell。
连接成功后可以快速验证:
ssh qwen "echo whoami=$(whoami); hostname; pwd"
正常应返回远端用户、容器主机名和工作目录。
十六、常见问题与踩坑记录
1. curl telnet://127.0.0.1:2222 一直卡住
这是本次实际遇到的问题。SSH 是持续交互协议,curl 连上后收到 SSH Banner,但不会完成 SSH 握手,于是双方会继续等待,看起来像“卡死”。
不要用无限等待的 curl telnet:// 检测 SSH。改用:
timeout 3 ssh-keyscan -p 2222 127.0.0.1
或者只检查监听:
ss -lntp | grep ':2222'
2. Permission denied (publickey,keyboard-interactive)
这通常说明网络、Tunnel、sshd 都已经通了,只是用户认证失败。
重点检查:
cat /root/.ssh/authorized_keys
ls -ld /root/.ssh
ls -l /root/.ssh/authorized_keys
并确认 Windows 使用的是对应私钥:
ssh -G qwen | Select-String 'identityfile|hostname|user|proxycommand'
3. Host key verification failed
这表示已经到达远端 SSH,但客户端不接受服务器 Host Key。第一次连接可以交互确认;如果容器被重建,Host Key 可能变化。
先确认确实是新的合法容器,再删除旧记录:
ssh-keygen -R qwen.example.com
然后重新连接并确认新指纹。不要在无法确认原因时无脑忽略 Host Key 检查。
4. cloudflared 提示缺少 cert.pem
如果 Tunnel 是在 Dashboard 创建的 remotely-managed tunnel,不需要 cert.pem。直接用 Dashboard 给出的 Tunnel Token 启动 Connector。
只有 locally-managed tunnel 才会涉及 cloudflared tunnel login、cert.pem 和本地 credentials JSON。
5. Tunnel 已 Healthy,但 SSH 仍失败
按下面顺序定位,不要同时乱改多处:
① Agent 本地能否 ssh 127.0.0.1:2222
② sshd 是否仍监听 127.0.0.1:2222
③ cloudflared 是否仍在运行
④ Tunnel Dashboard 是否 Healthy
⑤ Published Application 是否指向 ssh://127.0.0.1:2222
⑥ Windows ProxyCommand 是否正确
⑦ Windows 私钥是否与 authorized_keys 中公钥匹配
这个顺序可以快速判断问题在哪一层。
6. Agent 界面显示命令一直“处理中”
sshd -D 和 cloudflared tunnel run 本身都是长期运行进程,直接前台执行当然不会退出。
在临时测试阶段,可以使用 nohup ... &:
nohup /usr/sbin/sshd -D -e -f /tmp/agent_sshd_config >/tmp/sshd.log 2>&1 &
nohup cloudflared tunnel run --token-file /root/.cloudflared/qwen-tunnel.token >/tmp/cloudflared.log 2>&1 &
然后通过 pgrep 和日志判断状态,而不是等待命令返回。
但要注意:nohup 只能保证关闭终端后进程继续运行,不能解决容器/服务器重启后的自动恢复。如果 Agent 的 PID 1 是 supervisord,应使用下面的 Supervisor 持久化方案。
十七、临时 Agent 容器的生命周期问题
很多厂商 Agent 实际上是临时容器:
任务开始 → 创建容器 → Agent 工作 → 会话结束 → 容器销毁
如果容器被重建,下面这些内容可能全部消失:
/root/.ssh/authorized_keys/tmp/agent_sshd_config/etc/ssh/ssh_host_*sshd进程cloudflared进程/root/.cloudflared/qwen-tunnel.token
但 Cloudflare Dashboard 中的 Tunnel、Published Application、DNS 记录以及 Windows 的 ~/.ssh/config 可以继续保留。
因此每次新容器出现后,只需要重新完成“安装 sshd/cloudflared → 写公钥 → 启动 sshd → 启动 Tunnel Connector”即可。
如果容器频繁重建,建议把服务端初始化封装成脚本,并把 Tunnel Token 通过安全的 Secret/环境变量注入,而不是写死在公开脚本里。
Qwen Agent 实测:使用 Supervisor 实现重启自动恢复
本次 Qwen Agent 实际检查结果是:
ps -p 1 -o comm=
command -v systemctl
command -v cloudflared
输出类似:
supervisord
/usr/bin/systemctl
/usr/local/bin/cloudflared
这里最容易误判:系统里存在 systemctl 命令,不等于系统正在使用 systemd。 判断 init/进程管理器要看 PID 1;只要 ps -p 1 -o comm= 返回 supervisord,就应该使用 Supervisor,而不是 systemctl enable ...。
对于这种环境,推荐把 sshd 和 cloudflared 都交给 Supervisor 管理。
1. 把 SSH 配置从 /tmp 移到持久位置
前面的 /tmp/agent_sshd_config 适合临时测试,长期运行改为:
install -m 600 /tmp/agent_sshd_config /etc/ssh/qwen_sshd_config
sed -i 's#PidFile /tmp/agent-sshd.pid#PidFile /run/qwen-sshd.pid#' /etc/ssh/qwen_sshd_config
/usr/sbin/sshd -t -f /etc/ssh/qwen_sshd_config
最终核心配置仍保持:
Port 2222
ListenAddress 127.0.0.1
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PidFile /run/qwen-sshd.pid
2. Tunnel Token 使用独立文件保存
不要把真实 Token 直接写进 Supervisor 配置。继续使用:
/root/.cloudflared/qwen-tunnel.token
并限制权限:
chmod 600 /root/.cloudflared/qwen-tunnel.token
Supervisor 只通过 --token-file 引用它。
3. 为 sshd 创建启动包装器
如果之前已经通过 nohup 启动过旧 sshd,第一次迁移时可能与新实例争抢 127.0.0.1:2222。可以创建一个包装器:
cat >/usr/local/sbin/qwen-sshd-supervised <<'EOF'
#!/bin/sh
if pgrep -f -- '/usr/sbin/sshd -D -e -f /tmp/agent_sshd_config' >/dev/null 2>&1; then
pkill -f -- '/usr/sbin/sshd -D -e -f /tmp/agent_sshd_config' || true
sleep 1
fi
exec /usr/sbin/sshd \
-D \
-e \
-f /etc/ssh/qwen_sshd_config
EOF
chmod 755 /usr/local/sbin/qwen-sshd-supervised
4. 增加 Supervisor Program
本次 Qwen Agent 的实际 Supervisor 配置位于:
/etc/supervisor/conf.d/supervisord.conf
增加:
[program:qwen-sshd]
command=/usr/local/sbin/qwen-sshd-supervised
autostart=true
autorestart=true
startsecs=2
startretries=10
priority=15
stopsignal=TERM
stopasgroup=true
killasgroup=true
stdout_logfile=/var/log/qwen-sshd.log
stdout_logfile_maxbytes=5MB
stdout_logfile_backups=3
redirect_stderr=true
[program:qwen-cloudflared]
command=/usr/local/bin/cloudflared tunnel run --token-file /root/.cloudflared/qwen-tunnel.token
autostart=true
autorestart=true
startsecs=5
startretries=20
priority=25
stopsignal=TERM
stopasgroup=true
killasgroup=true
stdout_logfile=/var/log/qwen-cloudflared.log
stdout_logfile_maxbytes=5MB
stdout_logfile_backups=3
redirect_stderr=true
其中最关键的是:
autostart=true
autorestart=true
前者让 Supervisor 启动时自动启动进程,后者负责在进程异常退出后自动拉起。
本次环境还存在:
/etc/supervisor/conf.d/supervisord.conf.template
当前 supervisord.conf 是由这个模板展开生成的,因此为了避免容器重启后配置被平台重新生成覆盖,**同样的 [program:qwen-sshd] 和 [program:qwen-cloudflared] 块也要同步写入 .template**。
5. 不建议在线直接 HUP PID 1,优先重启验收
在这种一整个 Agent 都由同一个 Supervisor 管理的容器里,不建议为了加载两条新配置直接对 PID 1 执行 SIGHUP,因为可能连 Qwen App、Xvfb、XFCE 等其他程序一起重启。
更稳妥的方式是:先把配置全部写好并校验,再正常重启 Agent/容器。
重启以后验证:
pgrep -af 'sshd|cloudflared'
grep -n '^\[program:qwen-' \
/etc/supervisor/conf.d/supervisord.conf
本次实际重启验收后得到:
sshd: /usr/sbin/sshd -D -e -f /etc/ssh/qwen_sshd_config [listener]
/usr/local/bin/cloudflared tunnel run --token-file /root/.cloudflared/qwen-tunnel.token
[program:qwen-sshd]
[program:qwen-cloudflared]
随后 Windows 再通过 Cloudflare Tunnel 成功 SSH 进入 Agent,说明服务器端已经完成:
Qwen Agent 重启
↓
supervisord (PID 1)
├── qwen-sshd
│ └── 127.0.0.1:2222
│
└── qwen-cloudflared
└── Cloudflare Tunnel
至此,服务器端不再需要每次手动执行 nohup sshd ... 或 nohup cloudflared tunnel run ...。
注意:这只能解决“同一个持久容器重启”后的自动恢复。如果平台是“销毁旧容器并重新创建一个全新容器”,那么 /etc、/root/.ssh、Tunnel Token 等仍可能一起消失,此时仍应使用后面的初始化脚本或平台提供的持久卷/Secret/启动钩子。
十八、安全建议
这套方案虽然不需要开放公网 22 端口,但仍应把 SSH 和 Tunnel Token 当作高价值入口管理。
建议至少做到:
- SSH 禁止密码登录:保持
PasswordAuthentication no。 - 只使用公钥认证:每个设备/Agent 单独一把密钥,方便撤销。
- sshd 只监听 localhost:例如
127.0.0.1:2222,避免容器网络中的其他主机直接访问。 - 保护 Tunnel Token:不要发到聊天记录、Git 仓库或博客;泄露后应立即在 Cloudflare 侧轮换/重置。
- 不要公开私钥:
id_ed25519_qwen永远只留在本地可信设备。 - 可选叠加 Cloudflare Access:需要更严格访问控制时,可再增加身份认证或 Service Token。
- 尊重厂商规则:有些 Agent/沙箱明确禁止反向 Tunnel、后台守护进程或远程 Shell,使用前应确认服务条款。
尤其要注意:Tunnel Token 和 SSH 私钥是两种完全不同的凭据。
Tunnel Token
= 允许某台机器作为 Connector 接入你的 Cloudflare Tunnel
SSH Private Key
= 证明客户端有权登录远端 Linux 用户
两者都不能泄露。
十九、临时容器快速初始化脚本
如果厂商经常重建容器,可以把下面脚本保存为 bootstrap_ssh_tunnel.sh。使用前只需要准备:
SSH_PUBLIC_KEY:Windows 的.pub公钥TUNNEL_TOKEN:Cloudflare Dashboard 的 Tunnel Token
不要把包含真实 Token 的脚本提交到 Git。
#!/usr/bin/env bash
set -euo pipefail
: "${SSH_PUBLIC_KEY:?请设置 SSH_PUBLIC_KEY}"
: "${TUNNEL_TOKEN:?请设置 TUNNEL_TOKEN}"
if command -v apt-get >/dev/null 2>&1; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server curl procps
fi
if ! command -v cloudflared >/dev/null 2>&1; then
curl -L --fail --retry 3 \
-o /usr/local/bin/cloudflared \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x /usr/local/bin/cloudflared
fi
ssh-keygen -A
mkdir -p /root/.ssh /root/.cloudflared
chmod 700 /root/.ssh /root/.cloudflared
touch /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
grep -qxF "$SSH_PUBLIC_KEY" /root/.ssh/authorized_keys || \
echo "$SSH_PUBLIC_KEY" >> /root/.ssh/authorized_keys
cat >/tmp/agent_sshd_config <<'EOF'
Port 2222
ListenAddress 127.0.0.1
HostKey /etc/ssh/ssh_host_ed25519_key
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
UsePAM no
PidFile /tmp/agent-sshd.pid
Subsystem sftp internal-sftp
EOF
/usr/sbin/sshd -t -f /tmp/agent_sshd_config
pkill -f 'sshd -D -e -f /tmp/agent_sshd_config' 2>/dev/null || true
nohup /usr/sbin/sshd -D -e -f /tmp/agent_sshd_config \
>/tmp/sshd.log 2>&1 &
printf '%s\n' "$TUNNEL_TOKEN" >/root/.cloudflared/qwen-tunnel.token
chmod 600 /root/.cloudflared/qwen-tunnel.token
pkill -f 'cloudflared tunnel run' 2>/dev/null || true
nohup cloudflared tunnel run \
--token-file /root/.cloudflared/qwen-tunnel.token \
>/tmp/cloudflared.log 2>&1 &
sleep 3
echo '=== SSH ==='
ps aux | grep '[s]shd' || true
timeout 3 ssh-keyscan -p 2222 127.0.0.1 2>&1 || true
echo '=== CLOUDFLARED ==='
pgrep -af cloudflared || true
tail -n 20 /tmp/cloudflared.log || true
运行方式示例:
export SSH_PUBLIC_KEY='ssh-ed25519 AAAA... qwen-agent'
export TUNNEL_TOKEN='eyJ...'
bash bootstrap_ssh_tunnel.sh
unset TUNNEL_TOKEN
如果脚本输出 SSH Banner,且 Cloudflare 日志出现 Tunnel 注册成功信息,就可以回到 Windows 执行 ssh qwen。
如果旧版
cloudflared不支持--token-file,请先升级;临时测试也可以使用 Dashboard 给出的cloudflared tunnel run --token ...,但不要把 Token 写入公开日志或仓库。
二十、完整链路的判断标准
不要只看 Cloudflare 面板显示绿色。真正成功至少要同时满足:
Agent sshd ✅ 127.0.0.1:2222 正常监听
Agent 本地 SSH ✅ 公钥认证可用
Agent cloudflared ✅ Connector 正在运行
Cloudflare Tunnel ✅ Healthy / Connected
Published Application ✅ ssh://127.0.0.1:2222
Windows cloudflared ✅ 可执行
Windows SSH config ✅ ProxyCommand 正确
Windows → 远端 SSH ✅ 能完成 OpenSSH 握手和公钥认证
最终在 Windows 只需要:
ssh qwen
就可以进入原本完全没有公网 SSH 入口的厂商 Agent 容器。
二十一、cloudflared 常用命令速查与解释
cloudflared 不只是“SSH 工具”,它本质上是 Cloudflare 的 Tunnel/Access 客户端。最常用的命令可以分成四类:查看信息、运行 Tunnel、管理 Tunnel、作为客户端访问受 Cloudflare 保护的服务。
本文采用的是 Dashboard 创建的 remotely-managed Tunnel + Tunnel Token。下面表格中的
tunnel list/info/route dns/cleanup/delete属于账户级 Tunnel 管理命令,通常需要cloudflared tunnel login产生的账户证书或其他管理凭据;它们不是本教程 Agent 端启动 Connector 的必需命令。不要因为这些管理命令提示缺少cert.pem,就误以为tunnel run --token/--token-file也需要cert.pem。
| 命令 | 作用 | 典型使用场景 |
|---|---|---|
cloudflared version |
查看当前版本 | 确认安装是否成功、排查参数兼容性 |
cloudflared help |
查看顶层帮助 | 不记得命令时先查这里 |
cloudflared tunnel help |
查看 Tunnel 子命令 | 查询 Tunnel 相关参数 |
cloudflared tunnel run --token <TOKEN> |
使用 Dashboard Tunnel Token 启动 Connector | Agent/服务器主动接入 Cloudflare |
cloudflared tunnel run --token-file /path/token |
从文件读取 Token 启动 Connector | 比把 Token 长期暴露在命令行中更适合长期运行 |
cloudflared access ssh --hostname ssh.example.com |
通过 Cloudflare Access/Tunnel 访问 SSH | Windows ProxyCommand 的核心命令 |
cloudflared access login https://example.com |
登录 Cloudflare Access | 先完成浏览器身份认证,再访问受保护应用 |
cloudflared tunnel diag |
检查 Tunnel 网络环境 | 排查 DNS、Cloudflare API、出站连接异常 |
cloudflared tunnel list |
列出 Tunnel | CLI 管理 Tunnel 时查看名称和 UUID |
cloudflared tunnel info <NAME或UUID> |
查看指定 Tunnel/Connector 状态 | 排查当前连接情况 |
cloudflared tunnel route dns <TUNNEL> <HOSTNAME> |
为 Tunnel 创建 DNS 路由 | locally-managed Tunnel 常用;Dashboard 路由通常不需要手动执行 |
cloudflared tunnel cleanup <TUNNEL> |
清理异常退出后残留的 Connector 连接 | 主机断电、进程被强杀后清理旧连接记录 |
cloudflared tunnel delete <TUNNEL> |
删除 Tunnel | 确认不再使用后清理 Tunnel |
cloudflared tunnel --url http://127.0.0.1:3000 |
创建 Quick Tunnel | 临时把本地 Web 服务暴露到 trycloudflare.com |
cloudflared service install ... |
安装为系统服务 | 正常 Linux/Windows 长期服务器;临时容器通常没必要 |
cloudflared update |
自更新二进制 | 直接下载二进制安装时使用;包管理器安装更建议用对应包管理器升级 |
本教程最核心的两个命令方向一定要区分清楚:
Agent / 服务器端:
cloudflared tunnel run ...
↓
把本机服务“送进” Cloudflare Tunnel
Windows / 客户端:
cloudflared access ssh --hostname ...
↓
从 Cloudflare 把 SSH 连接“接出来”
因此本教程 Windows SSH 配置里的:
ProxyCommand cloudflared access ssh --hostname %h
实际意思是让 ssh.exe 不直接连接远端端口,而是把 SSH 数据交给 cloudflared,再由 Cloudflare 转入 Tunnel。也正因为如此,Windows 端不需要直接访问 Agent 的 2222 端口。
另外要特别区分两套 Tunnel 管理方式:Dashboard 创建的 remotely-managed tunnel 主要使用 Tunnel Token + tunnel run;cloudflared tunnel login、tunnel create、本地 cert.pem 和 credentials JSON 则属于 locally-managed tunnel 的另一套流程。没有必要把两套方式混在一起。
调试时,推荐按以下优先级使用命令:先用 cloudflared version 确认版本,再用 cloudflared tunnel diag 检查网络,随后看 cloudflared 运行日志;如果是 Windows SSH 失败,再用 ssh -v qwen 判断问题究竟发生在 ProxyCommand、Tunnel、sshd 还是公钥认证阶段。
二十二、官方文档与版本校验
本文于 2026-08-09 按 Cloudflare 当前官方文档重新核对,并在 Windows 本机确认 cloudflared 2026.7.3 的 tunnel run --help 中存在 --token-file 参数。后续如果 Cloudflare Dashboard 改版,优先以官方文档中的最新菜单名称和参数为准:
- Create a tunnel (dashboard)
- Connect to SSH with client-side cloudflared
- Connectivity pre-checks
- Tunnel permissions
总结
Cloudflare Tunnel 在这个场景中解决的是网络可达性:让只能主动出网的 Agent 容器反向接入 Cloudflare;OpenSSH 解决的是远程 Shell 与身份认证。二者组合后,可以在不开放公网端口、不要求公网 IP 的情况下获得标准 SSH 使用体验。
这套方法尤其适合可执行任意 Linux 命令、允许后台进程、但厂商没有提供 SSH 入口的开发 Agent、临时沙箱和容器环境。真正需要重点管理的是三件事:容器生命周期、Tunnel Token 和 SSH 私钥。