Qwen Agent + Cloudflare Tunnel:无公网 IP/端口容器 SSH 远程访问完整教程


Qwen Agent + Cloudflare Tunnel:无公网 IP/端口容器 SSH 远程访问完整教程

本文记录一次真实可用的 Qwen Agent 实践:Qwen 提供的 Linux Agent/容器环境可以执行命令并主动访问公网,但没有公网 IP,也没有给用户开放 SSH 端口。目标是从自己的 Windows 11 电脑直接 SSH 进入这个 Agent 容器。

为了避免把真实入口长期暴露在公开文章中,下面统一使用 qwen.example.com 作为示例域名;实际使用时替换为你在 Cloudflare 中配置的真实子域名即可。这套方法并不只适用于 Qwen,只要其他 Agent、沙箱或临时容器允许主动出网并运行 sshdcloudflared,同样可以复用。

一、最终实现效果

最终链路如下:

Windows 11
   │
   │ OpenSSH + cloudflared ProxyCommand
   ▼
Cloudflare Edge
   │
   │ Cloudflare Tunnel
   ▼
厂商 Agent / Linux 容器
   │
   ├── cloudflared(主动出站连接 Cloudflare)
   │
   └── sshd → 127.0.0.1:2222

整个过程中,Agent 一侧不需要开放任何公网入站端口。只要容器能够主动访问公网,并允许运行 cloudflaredsshd,就可以把本地 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                  # 能下载文件

如果 sshdcloudflared 不存在没有关系,后面安装即可。

还应测试基本公网访问:

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 runcloudflared tunnel diag 会自动做连通性预检,包括:

  • DNS:region1.v2.argotunnel.comregion2.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 logincert.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 -Dcloudflared 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 ...

对于这种环境,推荐把 sshdcloudflared 都交给 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 当作高价值入口管理。

建议至少做到:

  1. SSH 禁止密码登录:保持 PasswordAuthentication no
  2. 只使用公钥认证:每个设备/Agent 单独一把密钥,方便撤销。
  3. sshd 只监听 localhost:例如 127.0.0.1:2222,避免容器网络中的其他主机直接访问。
  4. 保护 Tunnel Token:不要发到聊天记录、Git 仓库或博客;泄露后应立即在 Cloudflare 侧轮换/重置。
  5. 不要公开私钥id_ed25519_qwen 永远只留在本地可信设备。
  6. 可选叠加 Cloudflare Access:需要更严格访问控制时,可再增加身份认证或 Service Token。
  7. 尊重厂商规则:有些 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 runcloudflared tunnel logintunnel 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.3tunnel run --help 中存在 --token-file 参数。后续如果 Cloudflare Dashboard 改版,优先以官方文档中的最新菜单名称和参数为准:

总结

Cloudflare Tunnel 在这个场景中解决的是网络可达性:让只能主动出网的 Agent 容器反向接入 Cloudflare;OpenSSH 解决的是远程 Shell 与身份认证。二者组合后,可以在不开放公网端口、不要求公网 IP 的情况下获得标准 SSH 使用体验。

这套方法尤其适合可执行任意 Linux 命令、允许后台进程、但厂商没有提供 SSH 入口的开发 Agent、临时沙箱和容器环境。真正需要重点管理的是三件事:容器生命周期、Tunnel Token 和 SSH 私钥


文章作者: 0xdadream
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 0xdadream !
评论
 上一篇
Cloudflare Tunnel 全功能实战指南:公网发布、SSH/RDP、私有网络、Access、高可用与排错 Cloudflare Tunnel 全功能实战指南:公网发布、SSH/RDP、私有网络、Access、高可用与排错
从零理解并部署 Cloudflare Tunnel,系统覆盖 Quick Tunnel、远程管理与本地管理 Tunnel、HTTP/HTTPS、SSH、TCP、RDP、私有 IP/域名、Access、Docker、自启动、高可用、监控和故障排查。
2026-08-09
下一篇 
Windows 11 句柄审查与泄漏定位教程 Windows 11 句柄审查与泄漏定位教程
Windows 11 句柄审查与泄漏定位教程
2026-06-01
  目录