通过SSH的反向连接功能实现从工作机向vps的反向打洞,然后我们就可以在公网访问vps的端口来登录工作机进行操作了。
核心脚本
这个脚本是在工作机上建立到vps的渠道tunnel。我用的是工作机的22端口,映射到vps的3006端口。
这个脚本是放在.oh-my-zsh的custom目录下自动加载的。
# ==============================================================================
# ./oh-my-zsh/custom/ssh_tunnel.zsh
# Oh-My-Zsh Custom Plugin: SSH Reverse Tunnel Keeper
# Description: 公司 WSL 与 腾讯云 VPS 之间的高可用 SSH 反向隧道管理工具
# ==============================================================================
# 基础配置项(根据实际网络与认证方式调整)
export TUNNEL_VPS_USER="*****"
export TUNNEL_VPS_IP="*********"
export TUNNEL_VPS_SSH_PORT="22" # 连接 VPS SSH 服务的端口
export TUNNEL_REMOTE_BIND_PORT="3006" # VPS 上对外暴露的监听端口
export TUNNEL_LOCAL_TARGET_PORT="22" # 公司 WSL 本地被转发的服务端口
export TUNNEL_IDENTITY_FILE="$HOME/.ssh/*****"
export TUNNEL_PID_FILE="$HOME/.ssh/tunnel_${TUNNEL_REMOTE_BIND_PORT}.pid"
# ------------------------------------------------------------------------------
# 内部函数:检测隧道进程存活状态
# ------------------------------------------------------------------------------
_tunnel_get_pid() {
if [[ -f "$TUNNEL_PID_FILE" ]]; then
local pid
pid=$(cat "$TUNNEL_PID_FILE" 2>/dev/null)
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
echo "$pid"
return 0
fi
fi
# PID 文件失效时,通过匹配命令行进程特征兜底查询
pgrep -f "ssh.*-R .*${TUNNEL_REMOTE_BIND_PORT}:localhost:${TUNNEL_LOCAL_TARGET_PORT}" | head -n 1
}
# ------------------------------------------------------------------------------
# 命令 [tunnel]: 反向隧道生命周期管理
# ------------------------------------------------------------------------------
tunnel() {
local cmd="$1"
case "$cmd" in
start)
local pid
pid=$(_tunnel_get_pid)
if [[ -n "$pid" ]]; then
echo "\033[33m[!] 隧道已在运行中,PID: $pid\033[0m"
return 0
fi
# 1. 检查 SSH 私钥权限与可用性
if [[ ! -f "$TUNNEL_IDENTITY_FILE" ]]; then
echo "\033[31m[x] 错误: 未找到 SSH 密钥文件: $TUNNEL_IDENTITY_FILE\033[0m"
echo "请确认已生成免密密钥并完成对 VPS 的公钥分发 (ssh-copy-id)。"
return 1
fi
# 2. 检查本地目标端口是否处于监听状态
if ! ss -tuln | grep -q ":${TUNNEL_LOCAL_TARGET_PORT} "; then
echo "\033[33m[!] 警告: 本地端口 :${TUNNEL_LOCAL_TARGET_PORT} 当前未处于 LISTEN 状态。\033[0m"
echo "打洞建立后,连接请求可能因目标服务未启动而被重置。"
fi
echo "\033[36m[*] 正在建立反向隧道 [VPS:${TUNNEL_REMOTE_BIND_PORT} -> Local:${TUNNEL_LOCAL_TARGET_PORT}]...\033[0m"
# 3. 构造打洞命令
# 采用长连接保活参数,防止内网防火墙空闲阻断连接
local ssh_options=(
-f -N
-i "$TUNNEL_IDENTITY_FILE"
-p "$TUNNEL_VPS_SSH_PORT"
-R "0.0.0.0:${TUNNEL_REMOTE_BIND_PORT}:localhost:${TUNNEL_LOCAL_TARGET_PORT}"
-o "ServerAliveInterval=30"
-o "ServerAliveCountMax=3"
-o "ExitOnForwardFailure=yes"
-o "StrictHostKeyChecking=accept-new"
-o "TCPKeepAlive=yes"
"${TUNNEL_VPS_USER}@${TUNNEL_VPS_IP}"
)
ssh "${ssh_options[@]}" 2>/dev/null
# 等待进程建立并验证状态
sleep 1
pid=$(_tunnel_get_pid)
if [[ -n "$pid" ]]; then
echo "$pid" > "$TUNNEL_PID_FILE"
echo "\033[32m[✓] 隧道建立成功!\033[0m"
echo " - 进程 PID : $pid"
echo " - 远端接入 : ${TUNNEL_VPS_IP}:${TUNNEL_REMOTE_BIND_PORT}"
echo " - 本地映射 : localhost:${TUNNEL_LOCAL_TARGET_PORT}"
else
rm -f "$TUNNEL_PID_FILE"
echo "\033[31m[x] 隧道建立失败!请排查以下原因:\033[0m"
echo " 1. 远端端口 :${TUNNEL_REMOTE_BIND_PORT} 是否已被占用?"
echo " 2. VPS 上的 /etc/ssh/sshd_config 是否设置了 GatewayPorts yes?"
echo " 3. 是否能直接免密登陆 VPS: ssh -i $TUNNEL_IDENTITY_FILE -p $TUNNEL_VPS_SSH_PORT ${TUNNEL_VPS_USER}@${TUNNEL_VPS_IP}"
return 1
fi
;;
stop)
local pid
pid=$(_tunnel_get_pid)
if [[ -z "$pid" ]]; then
echo "\033[33m[!] 未检测到运行中的隧道进程。\033[0m"
rm -f "$TUNNEL_PID_FILE" 2>/dev/null
return 0
fi
echo "\033[36m[*] 正在关闭隧道进程 (PID: $pid)...\033[0m"
kill "$pid" 2>/dev/null
sleep 0.5
if kill -0 "$pid" 2>/dev/null; then
kill -9 "$pid" 2>/dev/null
fi
rm -f "$TUNNEL_PID_FILE"
echo "\033[32m[✓] 隧道已成功终止。\033[0m"
;;
status)
local pid
pid=$(_tunnel_get_pid)
if [[ -n "$pid" ]]; then
echo "\033[32m[●] 隧道运行正常\033[0m"
echo " - PID : $pid"
echo " - 映射关系 : VPS:${TUNNEL_REMOTE_BIND_PORT} -> Local:${TUNNEL_LOCAL_TARGET_PORT}"
echo " - 目标主机 : ${TUNNEL_VPS_USER}@${TUNNEL_VPS_IP}"
else
echo "\033[31m[○] 隧道当前处于未连接状态。\033[0m"
rm -f "$TUNNEL_PID_FILE" 2>/dev/null
fi
;;
restart)
tunnel stop
sleep 1
tunnel start
;;
*)
echo "\033[36m使用方法:\033[0m tunnel {start|stop|restart|status}"
return 1
;;
esac
}
# ------------------------------------------------------------------------------
# 命令自动补全定义
# ------------------------------------------------------------------------------
_tunnel_completion() {
local -a commands
commands=(
'start:启动反向穿透隧道'
'stop:停止当前隧道连接'
'restart:重启隧道连接'
'status:查看隧道运行状态与进程信息'
)
_describe 'tunnel command' commands
}
compdef _tunnel_completion tunnel
上面其实核心的命令也就一条:
ssh -f -N -i ~/.ssh/tcent_CYsWork_id_rsa -p 22 -R 0.0.0.0:3006:localhost:22 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new -o TCPKeepAlive=yes <name>@<ip>
本质是利用 SSH 的“反向端口转发(Remote Port Forwarding)”机制,在工作电脑与公网 VPS 之间拉起一条加密管道。
具体参数含义如下:
1. 核心的打洞映射参数(最关键)
-R 0.0.0.0:3006:localhost:22: 这是反向打洞的灵魂。-R:代表 Remote(远端转发)。0.0.0.0:3006:要求 VPS 监听其所有网卡上的3006端口。:localhost:22:告诉 VPS,一旦有人访问 VPS 的3006端口,就把这个流量顺着刚才建立的 SSH 隧道送回给工作电脑本地的22端口(即工作电脑的 SSH/SFTP 服务)。
2. 后台运行参数
-f:让 SSH 在身份验证成功后,立即转入**后台(background)**运行,释放终端。-N:告诉 SSH 不执行任何远程命令。因为你只是为了搭桥(端口转发),不需要在 VPS 上打开交互式终端,加上-N更轻量、省资源。
3. 基础认证与连接参数
-i ~/.ssh/nameofrsa:指定连接 VPS 时使用的私钥文件(用于免密登录认证)。-p 22:连接 VPS 自身的 SSH 端口号(默认 22)。name@ip:登录 VPS 的用户身份与公网 IP。
4. 高可用与保活参数(防止内网断流)
-o ServerAliveInterval=30和-o ServerAliveCountMax=3: 工作电脑每隔 30 秒向 VPS 发送一次心跳包。如果连续 3 次没收到回应(90秒),判定连接断开。这是防止公司防火墙、NAT 路由器把空闲连接掐掉的关键配置。-o ExitOnForwardFailure=yes:如果 VPS 上的3006端口被占用了,或者打洞映射失败,命令会立即报错退出,而不是傻傻地挂在那里。-o StrictHostKeyChecking=accept-new:初次连接自动接收新的主机公钥指纹,方便脚本无人值守执行。-o TCPKeepAlive=yes:开启底层 TCP 层的保活探测。
前置任务
首先需要在本地将22端口监听上,不然就算通了,因为没有响应,你也连不上。
所以呢,最简单的就是用sudo service ssh start来在22端口启动ssh服务。
vps的设置
你的vps需要保证ssh配置有以下几个要点,也就是sudo nano /etc/ssh/sshd_config
# 允许反向隧道绑定到 0.0.0.0,使公网可访问穿透端口(核心)
GatewayPorts yes
# 允许 TCP 流量转发
AllowTcpForwarding yes
# 保持心跳连接,防止 NAT 超时静默断开
ClientAliveInterval 30
ClientAliveCountMax 3
# 保持连接活跃
TCPKeepAlive yes
你还需要在云服务商的管理页面以及vps中开启3006端口的防火墙放行规则。
sudo ufw enable 3006/tcp
sudo ufw restart
sudo ufw status
vps基本就好了,后面就可以在任何地方通过连接vps的3006端口来访问工作机了
终端的设置
对于需要登录到工作机的终端,理论上只需要跟通常一样ssh连接vps的3006端口就可以了,也可以参考之前的文章配置密钥登录,这里不多说,直接参考配置Debain Linux vps 使用ssh登录