做后端、运维或者数据相关工作的朋友大概率都遇到过这种场景MySQL 数据库安安静静地待在企业内网或者云内网里没有公网地址而你想在家里、客户现场或者临时工位上连上去查数据。直接mysql -h 10.0.0.5 -P 3306敲下去系统只会回你一句连接超时。这时候最常用、也最稳的办法就是标题里写的通过一台跳板机用 SSH 把远程 MySQL 的端口“借”到本地来。跳板机可以是内网里一台同时连通内网和外网的 Linux 主机也可以是专门做访问控制的堡垒机。你不需要给 MySQL 开放公网端口也不需要调整数据库所在网段的网络架构只要 SSH 能到跳板机就能把这条数据通道安全地拉起来。SSH 远程连接 MySQL 的做法本质上就是一条加密隧道。你在本地监听一个端口所有发往这个端口的数据都会被塞进 SSH 连接里传到跳板机之后再由跳板机转发到目标 MySQL。对 MySQL 来说访问来源是跳板机而不是你的本机。这个细节在后续授权时报错时会反复出现建议先记下来。这篇文章适合已经能正常用 SSH 登录跳板机、但对端口转发还不熟悉的开发者也适合要帮团队搭环境、又不想给生产库开公网权限的运维同事。后面我会从原理、命令、图形化客户端一直写到报错排查、自动保活和权限安全尽量把该避的坑都提前讲清楚。1. 先想明白为什么不能直接连数据库1.1 内网数据库连不上的真正原因不少人在第一次遇到问题时第一反应是“是不是 IP 写错了”。其实大多数情况不是 IP 错而是网络路由层面就不可达。数据库服务器通常只分配内网地址监听也只听内网网卡。内网地址本身是私有网段互联网上的路由器根本不知道 10.0.0.5 该怎么走所以从外网发过去的 TCP 包会直接石沉大海。还有一个更现实的层面即使数据库有公网 IP安全团队也强烈不建议你把 3306 直接暴露到公网。MySQL 这类数据库一旦暴露在公网会持续被扫描工具惦记暴力破解、漏洞尝试接踵而至。用一个生活类比来说数据库像是放在公司保险库里的重要资料你可以在公司内部自由查阅但绝对不能在公司大门外开一个窗口让路过的人都能翻看。所以内网数据库的访问路径不是“绕开网络限制”而是用正规的通道把请求送进去。跳板机就是这个通道的入口。1.2 跳板机在这条链路里做了什么跳板机是位于内网边缘的一台服务器它至少有一个内网地址同时可能有一个公网地址或者通过安全组映射让特定 IP 能访问它的 SSH 端口。你可以把它理解成公司的前台外部访客先经过前台登记也就是 SSH 登录再由前台工作人员带你去内部房间找到档案也就是 MySQL 服务。使用跳板机访问 MySQL有两个层次的做法。低阶做法是先 SSH 登录跳板机然后在跳板机的终端里执行mysql -h 10.0.0.5 -P 3306 ...。这样做能通但体验很差所有操作都在跳板机上执行本地拿不到 SQL 客户端的历史你没法用自己熟悉的图形工具而且跳板机要安装一堆客户端工具。高阶做法是本篇文章的主题在本地发起一条 SSH 连接用端口转发把远端 MySQL 的 3306 端口映射到本地某个端口。你的 MySQL 客户端跑在本地数据流量经过 SSH 加密后传到跳板机再由跳板机与内网的 MySQL 建立 TCP 连接。跳板机不需要安装 MySQL 客户端只需要一个能跑 SSH 服务的最小系统。有一点必须提醒SSH 隧道加密的是“本地到跳板机”这一段从跳板机到 MySQL 的那一段默认是内网明文 TCP 流量。如果跳板机和数据库之间跨越了不安全的网络或者你对数据保密性有硬性要求建议额外开启 MySQL 的 SSL 连接。不要以为上了 SSH 就万事大吉。2. 动手前的准备版本、账号和免密登录2.1 先检查本地到底缺什么在做任何端口转发之前先花两分钟确认本地的工具是齐的。ssh命令是最低要求现在 Windows 10/11、macOS、主流 Linux 发行版都自带 OpenSSH 客户端一般不用额外安装。打开终端执行ssh -V能看到版本信息就行。接下来检查 MySQL 客户端。如果你本地已经装了 MySQL通常有mysql命令执行mysql --version看看。如果没有客户端也没关系可以用图形工具后面会讲或者临时用容器起一个客户端docker run --rm -it mysql:8 mysql -h 127.0.0.1 -P 3307 -u dev -p执行这条命令前本地要有 Docker 环境并且127.0.0.1:3307已经转发到了目标 MySQL。容器的好处是不用污染本机。还有一个常见误区是以为跳板机上也必须装 MySQL 客户端。用-L端口转发方案时跳板机只是一个流量转发节点不需要装任何数据库组件。这能减少跳板机的维护成本也降低被滥用的风险面。2.2 数据库账号授权先按跳板机 IP 授权这是很多人踩坑最多次的环节。本地直连数据库时MySQL 看到的来源 IP 是本机 IP一旦经过跳板机来源 IP 就变成了跳板机到 MySQL 发起连接时所使用的 IP。所以你在授权时不能只给自己本机的 IP 授权还要给跳板机的 IP 授权。举个例子假设跳板机内网 IP 是10.0.0.6目标 MySQL 是10.0.0.5那么可以这样授权CREATE USER IF NOT EXISTS dev10.0.0.6 IDENTIFIED BY 换成强密码; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO dev10.0.0.6; FLUSH PRIVILEGES;如果目标 MySQL 和跳板机在同一台主机上转发命令里的目标地址写成localhost那么 MySQL 看到的来源地址往往是localhost或127.0.0.1相应地就要授权devlocalhost。如果你图省事直接授权dev%确实也能通但公网上任何来源都能尝试登录这个账号安全风险明显变大我不建议这么做。执行授权前先确认跳板机的内网 IP 是多少。登录跳板机后运行hostname -I或者ip addr就能看到。如果授权后发现还是无法连接去 MySQL 里执行SELECT user, host FROM mysql.user;查一下当前用户表能很清楚看到已有账号的 host 范围。2.3 配置 SSH 密钥把免密登录打通后面会用到-N、-f这些后台运行参数。如果跳板机还要交互式输入密码隧道就没法方便地后台常驻。所以强烈建议先配置 SSH 公钥登录。在本地生成密钥对ssh-keygen -t ed25519 -C for-mysql-jump一路回车就行默认会把私钥放在~/.ssh/id_ed25519。如果提示找不到ed25519算法也就是环境比较老可以换-t rsa -b 4096。然后把公钥复制到跳板机ssh-copy-id -i ~/.ssh/id_ed25519.pub jumpuser203.0.113.10没有ssh-copy-id的 Windows 环境可以手动把公钥内容追加到跳板机~/.ssh/authorized_keys文件里。追加完测试登录ssh jumpuser203.0.113.10如果能直接进入 shell 而不需要输密码就说明免密登录配置好了。这里203.0.113.10是示例的公网地址实际操作时替换成你自己的跳板机地址。配置好密钥后建议立刻检查私钥权限。Linux/macOS 下执行chmod 600 ~/.ssh/id_ed25519避免出现Permissions too open的报错。如果你在团队协作环境里私钥加密口令也很重要不要把裸私钥长期放在工作机上。3. 方案一一条 SSH 命令临时隧道说开就开3.1 端口转发原理不是“登录跳板机”而是“借用通道”很多人一听到 SSH 隧道第一反应是先ssh user跳板机登录进去然后在里面敲命令。但端口转发是另一回事你可以在不进入跳板机 shell 的情况下让本地和远端之间形成一条专用的数据通路。这条通路的工作流程可以这样理解本地 127.0.0.1:3307 --(SSH 加密通道)-- 跳板机 --(内网 TCP)-- MySQL 10.0.0.5:3306本地的 3307 端口只是一个“接待窗口”。你的 MySQL 客户端连接127.0.0.1:3307时数据被原封不动地打包进 SSH 加密连接传到跳板机后由跳板机解包再以跳板机自己的身份去连接10.0.0.5:3306。目标 MySQL 以为自己正在接待的是跳板机。之所以用 127.0.0.1 做监听地址是为了保证只有本机自己能访问这个转发端口。如果监听在0.0.0.0:3307同一局域网里的其他机器也能连你的隧道端口相当于你把内网数据库间接暴露给了整个局域网完全没有必要。3.2 临时隧道命令逐段拆解最常用的临时命令是这个ssh -N -L 127.0.0.1:3307:10.0.0.5:3306 jumpuser203.0.113.10 -p 22这条命令里的参数我拆开解释-N只做端口转发不申请远程 shell。如果不加这个参数SSH 登录后你会被丢进跳板机终端终端一关隧道也就断了。-Llocal forward含义是本地端口转发。左右两侧分别是本地监听地址和远端目标地址。127.0.0.1:3307本地监听的 IP 和端口。3307 是为了不和本地可能存在的 3306 冲突。10.0.0.5:3306跳板机视角下要访问的目标地址和端口。注意这个地址不是在本地解析而是在跳板机上解析。jumpuser203.0.113.10跳板机的登录用户和地址。-p 22跳板机 SSH 端口。如果是自定义端口比如 2222就改成-p 2222。如果跳板机登录密钥不是默认位置额外加-i ~/.ssh/xxx。如果目标 MySQL 就在跳板机本机把目标地址写成localhostssh -N -L 127.0.0.1:3307:localhost:3306 jumpuser203.0.113.10这里的localhost是在跳板机上执行的对应的是跳板机自己的 MySQL 服务不要理解成本地。3.3 隧道建立后用 mysql 客户端验证连接保持第一终端里的 SSH 隧道进程不要退出。打开第二个终端执行mysql -h 127.0.0.1 -P 3307 -u dev -p这里的-h 127.0.0.1尤其关键。如果你写成-h localhostMySQL 客户端可能会尝试通过 Unix socket 连接本地的 MySQL 服务可你本地往往没有运行 MySQL就会报ERROR 2002 (HY000): Cant connect to local MySQL server through socket...。指定127.0.0.1之后客户端走 TCP 协议才能真正连接到你转发出来的端口。保险起见也可以加上--protocolTCP。连接成功后可以执行SELECT hostname;看看返回的主机名是不是目标 MySQL 的主机名确认没有连错环境。临时隧道用完回到第一个终端按CtrlC结束 SSH 进程即可。4. 方案二SSH 配置文件把隧道变成日常习惯4.1 把常用参数写进 ~/.ssh/config命令行隧道的好处是随手就能起但如果每天都要连每次敲一长串命令就有点烦了。更可靠的做法是使用 SSH 配置文件在~/.ssh/config里给你常用的跳板机定义一个别名同时把端口转发参数一起写进去。下面是一个非常常用的配置片段Host jumpbox HostName 203.0.113.10 User jumpuser Port 22 IdentityFile ~/.ssh/id_ed25519 TCPKeepAlive yes ServerAliveInterval 30 ServerAliveCountMax 3 ExitOnForwardFailure yes LocalForward 13306 10.0.0.5:3306逐行解释一下关键项Host jumpbox是自己起的名字之后在命令行里写ssh jumpbox就会命中这个配置块LocalForward 13306 10.0.0.5:3306表示建立 SSH 连接时同时把本机 13306 转发到目标 MySQL 的 3306。其他的ServerAliveInterval和TCPKeepAlive是保活设置避免空闲时隧道被网络设备掐断ExitOnForwardFailure的作用是如果本地端口绑定失败SSH 直接退出而不是假装已建立免得调试时出现“隧道好像起了但又连不上”的尴尬。配置好之后只需要执行ssh -N jumpbox隧道就自动建好了。想验证本地监听lsof -i :13306然后用mysql -h 127.0.0.1 -P 13306 -u dev -p连接。4.2 多套环境并存也不怕记混端口工作中难免有开发库、测试库、预发库同时存在。如果每套环境都用不同跳板机可以在配置文件里定义多个 HostHost jump-prod HostName 203.0.113.10 User jumpuser LocalForward 13306 10.0.0.5:3306 Host jump-test HostName 198.51.100.20 User jumpuser LocalForward 13307 172.16.0.10:3306使用时分别执行ssh -N jump-prod和ssh -N jump-test。为了避免冲突不同环境用不同的本地端口例如生产用 13306测试用 13307。连接时端口对应好基本不会连错。还有一个经验把配置文件的属主和权限收紧。~/.ssh/config里包含服务器地址和用户名虽然不是密钥但也算敏感信息。Linux/macOS 下执行chmod 600 ~/.ssh/config避免其他用户能读到你的连接信息。文件权限如果太宽松SSH 客户端可能拒绝加载这个配置。5. 方案三图形化客户端的 SSH 通道设置5.1 MySQL Workbench 的 Standard TCP/IP over SSH并不是所有人都习惯命令行。很多人连数据库只用图形界面。主流的 MySQL 图形客户端基本都内置了 SSH 隧道能力不需要你先在终端里开启端口转发。MySQL Workbench 新建连接时连接方式选择Standard TCP/IP over SSH然后会看到两套填写区。SSH 区填跳板机信息SSH Hostname填跳板机地址SSH Username填跳板机登录用户名认证方式选择公钥时指定私钥文件。MySQL 区填数据库信息MySQL Hostname填内网 MySQL 的实际地址MySQL Server Port填 3306用户名密码填数据库账号。这里要特别注意MySQL Workbench 的MySQL Hostname不是填127.0.0.1除非你的 MySQL 就跑在跳板机本机。它的做法是先建立到跳板机的 SSH 连接然后由 Workbench 带着 SSH 通道去访问你填写的那个 MySQL 内网地址。如果这里填错隧道能建立但访问 MySQL 时还是会失败。5.2 Navicat、DBeaver 一类的工具也大同小异我平时用到的好几款客户端逻辑都差不多。以你常用的工具为例通常主连接配置里填数据库的内网地址、端口、账号然后单独有一个 SSH 标签页勾选“使用 SSH 隧道”再填跳板机地址、端口、用户名和密码或私钥。工具会替你完成建隧道的动作不需要你在外面先跑一条ssh -L命令。如果你更习惯命令行先开隧道那么图形工具也可以按“本地连接”的方式配置MySQL 主机填127.0.0.1端口填你转发出来的 3307 或 13306。这两种方式选一种就好不要同时勾选工具的 SSH 隧道又额外开本地转发否则连接链路会被搞乱排错也不方便。图形工具报错往往不够直观遇到问题我建议先把命令行方案调通再回填到图形工具里。命令行能通说明网络、账号、授权都没问题剩下只是工具配置项的对应关系。这一点可以帮你节省大量时间。6. 故障排查我实际遇到过的高频坑6.1 高频报错速查表下面这张表是我在实际排障中整理出来的胜在可以直接对着翻。排查时先看报错文案里的关键词再对照可能原因和处理方向比盲目改配置要高效很多。现象 / 报错可能原因处理方向ssh: connect to host ... Connection timed out跳板机 SSH 端口不可达检查跳板机安全组和防火墙是否放行 SSH 端口以及当前 IP 是否在白名单Permissions 0644 for id_ed25519 are too open私钥文件权限过松Linux/macOS 执行chmod 600 ~/.ssh/id_ed25519Windows 下取消继承权限bind: Address already in use本地端口已被占用换一个本地端口或先用lsof -i :端口号找到占用进程ERROR 2002 ... socket /var/run/mysqld/mysqld.sock-h localhost走了 socket 而非 TCP改成-h 127.0.0.1必要时加--protocolTCPCant connect to MySQL server on 127.0.0.1隧道还没建立或目标端口没通先确认 SSH 隧道进程还在再查跳板机到 MySQL 的连通性Access denied for user dev10.0.0.6MySQL 授权里没有包含跳板机来源 IP按跳板机 IP 重新授权或者授权%不推荐Lost connection ... reading initial communication packet跳板机到 MySQL 之间网络不通或 MySQL 拒绝非预期来源检查安全组、防火墙和 MySQL 的 bind-address、skip-networking 设置No supported authentication methods available跳板机只允许密钥登录客户端没带正确私钥用-i指定私钥或先在 ssh-agent 里加载密钥6.2 最容易被误解的“来源 IP 不对”我在帮同事排查时遇到最多的不是网络问题而是授权问题。现象是在本地直连 MySQL 没问题但加了 SSH 隧道之后一直报Access denied。细看报错里的来源 IP像dev10.0.0.6这个地址既不是你本机的公网 IP也不是 MySQL 内网地址而是跳板机到 MySQL 发起 TCP 连接时使用的 IP。原因在前文原理部分说过隧道建立后真正连接 MySQL 的是跳板机。所以 MySQL 判断客户端来源时看到的永远是跳板机。此时解决办法很简单登录 MySQL执行SELECT user, host FROM mysql.user;看看有没有匹配的账号没有就补一个CREATE USER dev10.0.0.6或者GRANT ... TO dev10.0.0.6。若 MySQL 与跳板机同机来源则可能是localhost授权对象也要相应调整。这里我还想多说一句不要因为嫌麻烦就直接把 MySQL 的 host 改成%。一旦改成%任何机器只要网络能到达数据库且有账号密码都能尝试登录。哪怕数据库只在内网也要防着内网横向渗透。按最小权限模型管理才是长期安稳的做法。6.3 网络问题的三步定位法如果报错不是权限而是连接超时或“失去连接”我会按下面三步快速定位第一步确认 SSH 本身能通。执行ssh -v jumpuser203.0.113.10重点看是否出现Authentication succeeded。如果 SSH 都不通后面全是白搭。第二步确认跳板机能访问 MySQL。通过 SSH 登录跳板机后执行nc -vz 10.0.0.5 3306或者用telnet 10.0.0.5 3306。看到 succeeded 说明跳板机到 MySQL 的网络路径没问题如果超时问题多半在 MySQL 所在主机的防火墙、安全组或者 MySQL 没有监听预期地址。第三步确认本地隧道真的在工作。执行ssh -N -L 127.0.0.1:3307:10.0.0.5:3306 -v jumpuser203.0.113.10观察有没有Local forwarding listening之类的输出另开终端执行lsof -i :3307只要 LISTEN 状态在说明端口已经挡住。这时本地再连127.0.0.1:3307就顺理成章了。7. 进阶玩法让隧道更稳也更安全7.1 用 autossh 自动拉起断掉的隧道普通 SSH 隧道最大的问题是网络一抖就断而你不会时刻盯着终端。生产需要长期稳定的连接时可以引入autossh。安装方式各个系统不一样macOS 用brew install autosshDebian/Ubuntu 用apt install autosshCentOS 系用yum install autossh。启动命令很简单autossh -M 0 -N -L 127.0.0.1:13306:10.0.0.5:3306 jumpuser203.0.113.10-M 0表示不额外启用 autossh 自己的监控端口让它依赖 SSH 连接本身的老化探测。为了探测有效记得在~/.ssh/config里给对应 Host 配置ServerAliveInterval 30和ServerAliveCountMax 3。这样 SSH 会每 30 秒发一个 keepalive 包连续 3 次没响应就判定连接失效autossh 会重新拉起一条新的隧道。如果跳板机的端口转发还要开机自启可以再写一个 systemd service 管理 autossh 进程设置Restartalways。这样只要机器不关机隧道基本能保持在线。7.2 多隧道场景的整理套路我经历过同时维护开发、测试、生产三套数据库的场景。如果全部用命令行后台跑时间一长容易分不清哪个端口对应哪套环境。我的做法是在~/.ssh/config里按环境写清楚 Host 别名和 LocalForward同时把本地端口固定成有规律的数字比如 13306 对应生产、13307 对应测试、13308 对应开发。连接数据库前先ssh -N jump-prod再连-P 13306不会搞混。还有一个技巧是利用 SSH Connection Multiplexing 复用连接。在配置里加入ControlMaster auto ControlPath ~/.ssh/cm-%r%h:%p ControlPersist 10m这组成员会让同一个跳板机的后续 SSH 连接复用第一次建立的 TCP 连接减少反复握手的时间。对于频繁开隧道、频繁断开的开发机体验提升非常明显。缺点是需要额外维护 Socket 文件不用的时候可以通过ssh -O exit jumpbox主动关闭。7.3 安全底线不要为了图方便把公网端口打开最后这部分我必须多说几句。跳板机方案本身就是为了避免把数据库暴露到公网但如果你在使用过程中忽视安全设置反而会引入新的风险。以下几点是我实际生产环境里一直坚持的底线第一MySQL 永远不要开公网监听。数据库的bind-address保持内网地址安全组里不要放行 3306 到所有来源。第二跳板机只保留必要的 SSH 服务不要在上面装一大堆应用。第三SSH 私钥必须设置 passphrase并配合ssh-agent使用避免私钥文件一旦泄露就被人直接登录。第四数据库账号遵循最小权限能只给某个库的只读权限就不要给全局权限不要把 root 密码投喂给每位开发者。第五如果团队规模大、审计要求高建议把普通跳板机替换成专业堡垒机至少要对操作过程做审计记录。这里再补充一个我经常被问到的点SSH 隧道只保护本地到跳板机这一段跳板机到 MySQL 是否加密取决于 MySQL 自身的 SSL 配置。如果业务数据非常敏感我建议为 MySQL 开启 SSL或者在网络设计上保证跳板机到数据库之间也处于可信的内网隔离环境。不要以为上了 SSH 隧道整条链路就都加密了。最后分享一个我自己的习惯凡是需要长期存在的隧道我都会写成配置文件并且只用 127.0.0.1 做监听地址临时排查的时候才用一条带-v的ssh -L命令看完整建链过程。数据库端口永远只对跳板机开放这比任何复杂的白名单都好维护。跳板机 SSH 连接 MySQL 这件事原理通了后面换任何工具、任何环境都只是拼参数。