SSH隧道连私有子网数据库本质是通过堡垒机端口转发实现安全访问,流量路径为本地→堡垒机(SSH加密)→数据库(内网直连),需正确配置端口映射、用户授权、防火墙及安全组,并注意host使用127.0.0.1而非localhost。
用 SSH 隧道连私有子网数据库,本质是“跳一次”
堡垒机不是中转站,而是你和数据库之间唯一被允许的 ssh 入口。数据库本身不暴露任何端口到公网,也不需要开 ssh 服务——你只对堡垒机 ssh,再让这台机器帮你把本地端口(比如 3306)转发到它能访问的数据库内网地址(比如 10.0.0.5:3306)。流量路径是:本地 → 堡垒机(ssh 加密)→ 数据库(纯内网直连),中间没有额外隧道或代理层。
常见错误现象:
• 本地执行 mysql -h 127.0.0.1 -P 3307 报 Connection refused —— SSH 隧道根本没建起来,或端口映射方向写反;
• 连上了但查不到数据,提示 Access denied for user —— 数据库用户没授权给 'user'@'10.0.0.5'(即堡垒机内网 IP),只给了 'user'@'localhost';
• 隧道建立后几分钟就断开 —— 缺少 ServerAliveInterval 保活配置,被中间网络设备踢掉。
- 实操命令示例(Linux/macOS):
ssh -L 3307:10.0.0.5:3306 -N -f -o ServerAliveInterval=60 user@bastion.example.com -
-L 3307:10.0.0.5:3306表示:本机3307端口 → 堡垒机 → 转发到10.0.0.5:3306(数据库真实地址) -
-N表示不执行远程命令,只做端口转发;-f让 SSH 在后台运行 - Windows 用户可用 PuTTY 或 Windows Terminal + OpenSSH,参数逻辑一致,只是界面填空
数据库用户权限必须匹配堡垒机的内网出口 IP
很多人卡在这一步:数据库明明运行正常,SSH 隧道也显示成功,但客户端连上 127.0.0.1:3307 就报认证失败。原因很直接——数据库用户授权范围写死了 localhost 或某个具体客户端 IP,而实际连接来源是堡垒机的内网 IP(比如 10.0.0.10)。
- MySQL 检查授权:
SELECT User, Host FROM mysql.user WHERE User = 'your_db_user'; - 如果结果里 Host 是
localhost,需改成10.0.0.10(堡垒机在 VPC 内的 IP)或更安全的10.0.0.%(限定子网段) - PostgreSQL 对应检查
pg_hba.conf,确保有类似这一行:host all your_db_user 10.0.0.10/32 md5 - 切勿用
%开放所有主机 —— 这等于绕过堡垒机的安全意义
别让防火墙或安全组“悄悄拦住”隧道流量
SSH 隧道本身只依赖堡垒机的 22 端口通,但很多人忘了:数据库服务器上的防火墙(如 ufw、iptables)或云平台安全组,仍可能拒绝来自堡垒机 IP 的数据库端口访问。
- 检查数据库服务器是否监听内网地址:
ss -tlnp | grep :3306,确认是0.0.0.0:3306或10.0.0.5:3306,而非仅127.0.0.1:3306 - 腾讯云/AWS/阿里云控制台中,数据库实例所在安全组要放行来自堡垒机内网 IP 的
3306(或5432)端口,而不是放行公网 IP 或0.0.0.0/0 - 若数据库用的是 RDS 类托管服务,通常无需改系统防火墙,但必须在 RDS 安全组里加规则;自建数据库则两头都要查
想批量操作?Ansible 或脚本化时注意连接复用
运维中常需要从跳板机批量跑 SQL 或备份,这时直接写死 ssh -L 不现实。关键是让 Ansible 或脚本走已建立的 SSH 连接,避免每次新建隧道带来的延迟和不稳定。
- Ansible 中启用 ControlPersist:
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=~/.ansible/cp/%r@%h:%p(写进ansible.cfg) - 这样后续任务复用同一个 SSH 连接,
proxy_command可设为:ssh -W %h:%p -q bastion.example.com - Python 脚本用
paramiko建隧道时,务必设置set_keepalive(30),否则空闲超时会断连 - 临时调试可用
nc -zv 10.0.0.5 3306从堡垒机上直连测试,比反复启停隧道快得多
最常被忽略的一点:隧道建立后,数据库连接字符串里的 host 必须写 127.0.0.1,不能写 localhost —— 后者在 MySQL 中会强制走 socket 文件,绕过 TCP 和你设的端口映射。

















