利用ssh打洞到VPS来连接工作机

通过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登录

内容声明与版权协议

  • 版权协议:本文采用 CC BY-NC-SA 4.0(署名-非商业性使用-相同方式共享) 协议进行许可。转载、演绎请务必保留作者署名及原文链接,且不得用于商业目的。
  • AI 辅助说明:本博客部分内容(如排版设计、个别局部、特定对话片段等)可能由 AI(LLM大语言模型)辅助生成。作者在此明确声明:本人对文章的核心框架、思想立论与最终呈现拥有绝对主导权与最终解释权。
  • 观点免责:文中明示的由 AI 生成的特定文本、对话输出,仅作为理性探讨以及观点展示,不代表本人真实立场、价值观或事实背书。对于因 AI 幻觉造成的客观事实偏差,请读者审慎甄别。
  • 认知时效性:文章仅代表作者在撰写当下的认知状态与情绪切片。随着个人心智迭代与客观事物发展,作者观点可能发生演变。欢迎基于理性的交流与批评。