SSH 隧道:安全连接远程数据库的秘密通道
为什么数据库只监听 127.0.0.1?如何安全地远程访问?
December 27, 2024·16 min read·Yimin
#SSH#数据库#安全#端口转发#运维
数据库只监听 127.0.0.1,但你需要从本地连接它。SSH 隧道就是那条安全的秘密通道。
🎯 为什么需要 SSH 隧道?
真实场景:安全 vs 便利的矛盾
你有一台云服务器,上面运行着 PostgreSQL 和 Redis:
┌─────────────────────────────────────────────────────────────────┐
│ 云服务器 │
│ │
│ PostgreSQL: 127.0.0.1:5432 ← 只监听本地 │
│ Redis: 127.0.0.1:6379 ← 只监听本地 │
│ │
│ 为什么这样配置? │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 安全!不暴露到公网,防止被扫描和攻击 │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
问题:你想用 Navicat 连接数据库,怎么办?
三种方案对比
┌─────────────────────────────────────────────────────────────────┐
│ 方案一:暴露端口到公网 │
│ │
│ 配置: PostgreSQL 监听 0.0.0.0:5432 │
│ │
│ 互联网 ────────────────→ 服务器:5432 │
│ 任何人都能访问! │
│ │
│ ❌ 极度危险 │
│ ❌ 会被扫描器发现 │
│ ❌ 暴力破解风险 │
│ ❌ 可能泄露数据 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 方案二:防火墙白名单 │
│ │
│ 配置: 只允许特定 IP 访问 5432 端口 │
│ │
│ 你的 IP ──────────────→ 服务器:5432 ✅ │
│ 其他 IP ──────────────→ 服务器:5432 ❌ │
│ │
│ ⚠️ 稍微安全 │
│ ❌ 家庭/办公网络 IP 经常变化 │
│ ❌ 需要频繁更新白名单 │
│ ❌ 数据传输未加密 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 方案三:SSH 隧道 ⭐ │
│ │
│ 配置: 数据库只监听 127.0.0.1,通过 SSH 访问 │
│ │
│ 你的电脑 ───[SSH 加密隧道]───→ 服务器 ───→ 127.0.0.1:5432 │
│ │
│ ✅ 最安全 │
│ ✅ 数据全程加密 │
│ ✅ 复用 SSH 认证(密钥/密码) │
│ ✅ 不暴露数据库端口 │
└─────────────────────────────────────────────────────────────────┘
🔍 SSH 隧道的工作原理
什么是 SSH 隧道?
SSH 隧道(SSH Tunnel)是一种通过 SSH 连接转发网络流量的技术。
┌─────────────────────────────────────────────────────────────────┐
│ SSH 隧道原理图 │
│ │
│ 你的电脑 云服务器 │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ │ │ │ │
│ │ Navicat │ │ PostgreSQL │ │
│ │ 连接 │ │ 127.0.0.1 │ │
│ │ 127.0.0.1 │ SSH 加密隧道 │ :5432 │ │
│ │ :15432 │ =================> │ │ │
│ │ ↓ │ │ ↑ │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ SSH 客户 │ │ TCP:22 加密 │ │ SSH 服务 │ │ │
│ │ │ 端 │ ←───────────────────→ │ │ 端 │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │
│ │ │ │ │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ 数据流向: │
│ Navicat → 本地 15432 → SSH 加密 → 服务器 SSH → 服务器 5432 │
└─────────────────────────────────────────────────────────────────┘
核心概念:本地端口转发
SSH 隧道的核心是本地端口转发(Local Port Forwarding):
┌─────────────────────────────────────────────────────────────────┐
│ 本地端口转发命令 │
│ │
│ ssh -L 15432:127.0.0.1:5432 user@server │
│ ↑ ↑ ↑ ↑ │
│ │ │ │ │ │
│ │ │ │ └── 远程服务端口 │
│ │ │ └─────────── 远程服务地址(服务器视角) │
│ │ └────────────────── 本地监听端口 │
│ └───────────────────── 标志:Local 本地转发 │
│ │
│ 效果: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 访问本地 127.0.0.1:15432 │ │
│ │ ↓ │ │
│ │ 流量被 SSH 加密,发送到服务器 │ │
│ │ ↓ │ │
│ │ 服务器将流量转发到 127.0.0.1:5432(服务器本地的 PG) │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
为什么远程地址是 127.0.0.1?
┌─────────────────────────────────────────────────────────────────┐
│ 关键理解:视角问题 │
│ │
│ 命令: ssh -L 15432:127.0.0.1:5432 user@server │
│ └─────────────┘ │
│ 这个地址是从服务器的视角看的! │
│ │
│ 服务器视角: │
│ ┌─────────────────────────────────────────────┐ │
│ │ 服务器 │ │
│ │ │ │
│ │ 127.0.0.1:5432 → PostgreSQL 在监听这里 │ │
│ │ │ │
│ │ SSH 收到转发请求后, │ │
│ │ 连接的是服务器本地的 127.0.0.1:5432 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 不是你电脑的 127.0.0.1,是服务器的 127.0.0.1! │
└─────────────────────────────────────────────────────────────────┘
🎨 Navicat 的 SSH 隧道实现
Navicat 帮你做了什么?
当你在 Navicat 中配置 SSH 隧道时,Navicat 自动完成了所有底层操作:
┌─────────────────────────────────────────────────────────────────┐
│ Navicat 配置界面 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ General 标签页 │ │
│ │ │ │
│ │ Host: 127.0.0.1 ← 目标数据库地址(服务器视角) │ │
│ │ Port: 5432 ← 目标数据库端口 │ │
│ │ Password: ****** ← 数据库密码 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SSH 标签页 │ │
│ │ │ │
│ │ ☑ Use SSH Tunnel ← 启用 SSH 隧道 │ │
│ │ Host: your-server-ip ← SSH 服务器 IP │ │
│ │ Port: 22 ← SSH 端口 │ │
│ │ User: root ← SSH 用户名 │ │
│ │ Auth: Private Key ← 认证方式 │ │
│ │ Key: ~/.ssh/id_ed25519 ← 私钥文件路径 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
连接流程详解
┌─────────────────────────────────────────────────────────────────┐
│ 连接流程(7 步) │
│ │
│ 第 1 步: 建立 SSH 连接 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Navicat → SSH 服务器 (your-server-ip:22) │ │
│ │ 使用私钥 ~/.ssh/id_ed25519 进行认证 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 2 步: 请求端口转发 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Navicat 告诉 SSH:请把流量转发到 127.0.0.1:5432 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 3 步: 本地监听 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Navicat 在本地开启一个临时端口(如 127.0.0.1:随机端口) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 4 步: 数据库连接 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Navicat 的数据库客户端连接到本地临时端口 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 5 步: 流量转发 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 本地端口的流量 → SSH 加密 → 服务器 → 127.0.0.1:5432 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 6 步: 数据库响应 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL 响应 → SSH 加密返回 → Navicat 收到数据 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 7 步: 连接成功 ✅ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 你可以愉快地查询数据库了! │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
🔐 安全性分析
为什么 SSH 隧道更安全?
┌─────────────────────────────────────────────────────────────────┐
│ 安全性对比 │
│ │
│ 直接暴露数据库端口: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 互联网 ─────────────────────────────→ PostgreSQL:5432 │ │
│ │ │ │
│ │ 攻击面: │ │
│ │ • 端口扫描器能发现 │ │
│ │ • 可被暴力破解密码 │ │
│ │ • 数据明文传输(除非配置 SSL) │ │
│ │ • 0-day 漏洞可直接利用 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ SSH 隧道: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 互联网 ────→ SSH:22 ────→ 127.0.0.1:5432 │ │
│ │ (唯一入口) (不对外暴露) │ │
│ │ │ │
│ │ 安全保障: │ │
│ │ ✅ 只暴露 SSH 端口(22) │ │
│ │ ✅ SSH 协议成熟,安全性高 │ │
│ │ ✅ 支持密钥认证(无密码) │ │
│ │ ✅ 所有流量加密传输 │ │
│ │ ✅ 数据库漏洞无法从外部利用 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
SSH 密钥认证 vs 密码认证
┌─────────────────────────────────────────────────────────────────┐
│ 认证方式对比 │
│ │
│ 密码认证: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 用户名 + 密码 → 服务器验证 │ │
│ │ │ │
│ │ 风险: │ │
│ │ • 密码可能被暴力破解 │ │
│ │ • 密码可能泄露(钓鱼、键盘记录) │ │
│ │ • 传输过程中虽然加密,但仍有风险 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 密钥认证 ⭐: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 私钥(你电脑上) 公钥(服务器上) │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ id_ed25519 │ ←─配对─→ │ authorized │ │ │
│ │ │ (绝不外传) │ │ _keys │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ 认证流程: │ │
│ │ 1. 服务器发送随机挑战 │ │
│ │ 2. 客户端用私钥签名 │ │
│ │ 3. 服务器用公钥验证签名 │ │
│ │ 4. 私钥从不离开你的电脑! │ │
│ │ │ │
│ │ 优势: │ │
│ │ ✅ 无法暴力破解 │ │
│ │ ✅ 私钥不传输 │ │
│ │ ✅ 可禁用密码登录,只允许密钥 │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
🛠️ 命令行方式(进阶)
手动建立 SSH 隧道
如果不用 Navicat,你也可以手动建立隧道:
# PostgreSQL 隧道
ssh -L 15432:127.0.0.1:5432 -N -f root@your-server-ip -i ~/.ssh/id_ed25519
# Redis 隧道
ssh -L 16379:127.0.0.1:6379 -N -f root@your-server-ip -i ~/.ssh/id_ed25519
参数解释:
┌─────────────────────────────────────────────────────────────────┐
│ SSH 隧道参数 │
│ │
│ -L 15432:127.0.0.1:5432 │
│ │ │ │ │
│ │ │ └── 远程端口 │
│ │ └────────────── 远程地址(服务器视角的 localhost) │
│ └──────────────────── 本地端口(你电脑上监听的端口) │
│ │
│ -N 不执行远程命令,只做端口转发 │
│ -f 后台运行 │
│ -i 指定私钥文件 │
│ │
│ 建立后: │
│ 连接 127.0.0.1:15432 → 等于连接服务器的 PostgreSQL │
│ 连接 127.0.0.1:16379 → 等于连接服务器的 Redis │
└─────────────────────────────────────────────────────────────────┘
使用示例
# 1. 建立隧道
ssh -L 15432:127.0.0.1:5432 -N -f root@your-server-ip -i ~/.ssh/id_ed25519
# 2. 用 psql 连接(就像连本地数据库一样)
psql -h 127.0.0.1 -p 15432 -U postgres -d postgres
# 3. 或用 redis-cli 连接 Redis
ssh -L 16379:127.0.0.1:6379 -N -f root@your-server-ip -i ~/.ssh/id_ed25519
redis-cli -h 127.0.0.1 -p 16379 -a "your_password"
🚨 常见问题与解决方案
1. 连接失败:Failed to resolve hostname
┌─────────────────────────────────────────────────────────────────┐
│ 错误: Failed to resolve hostname xxx (nodename not known) │
│ │
│ 原因: │
│ • 代理软件(ClashX、Surge)劫持了 DNS 解析 │
│ • 网络配置问题 │
│ │
│ 解决: │
│ 1. 检查代理软件设置,排除 SSH 流量 │
│ 2. 使用 IP 地址而非域名 │
│ 3. 检查 /etc/hosts 配置 │
└─────────────────────────────────────────────────────────────────┘
2. 连接失败:Connection refused
┌─────────────────────────────────────────────────────────────────┐
│ 错误: Connection refused │
│ │
│ 可能原因: │
│ • 数据库服务未运行 │
│ • 端口配置错误 │
│ • 数据库未监听指定地址 │
│ │
│ 排查命令: │
│ # SSH 到服务器后检查 │
│ docker ps | grep postgres │
│ docker ps | grep redis │
│ netstat -tlnp | grep 5432 │
└─────────────────────────────────────────────────────────────────┘
3. 认证失败:Permission denied
┌─────────────────────────────────────────────────────────────────┐
│ 错误: Permission denied (publickey) │
│ │
│ 可能原因: │
│ • 私钥路径错误 │
│ • 私钥文件权限不正确 │
│ • 服务器上没有对应的公钥 │
│ │
│ 排查步骤: │
│ 1. 确认私钥路径正确 │
│ 2. 检查权限: chmod 600 ~/.ssh/id_ed25519 │
│ 3. 测试 SSH: ssh -i ~/.ssh/id_ed25519 root@your-server-ip │
└─────────────────────────────────────────────────────────────────┘
4. 连接超时
┌─────────────────────────────────────────────────────────────────┐
│ 错误: Connection timed out │
│ │
│ 可能原因: │
│ • 服务器防火墙阻止 22 端口 │
│ • 网络不通 │
│ • SSH 服务未运行 │
│ │
│ 排查: │
│ ping your-server-ip # 检查网络连通性 │
│ telnet your-server-ip 22 # 检查 SSH 端口是否开放 │
└─────────────────────────────────────────────────────────────────┘
🎯 最佳实践
1. 数据库安全配置
┌─────────────────────────────────────────────────────────────────┐
│ 推荐配置: │
│ │
│ PostgreSQL (postgresql.conf): │
│ listen_addresses = '127.0.0.1' # 只监听本地 │
│ │
│ Redis (redis.conf): │
│ bind 127.0.0.1 # 只监听本地 │
│ requirepass <strong_password> # 设置密码 │
│ │
│ Docker Compose: │
│ ports: │
│ - "127.0.0.1:5432:5432" # 只绑定到 localhost │
└─────────────────────────────────────────────────────────────────┘
2. SSH 安全加固
┌─────────────────────────────────────────────────────────────────┐
│ /etc/ssh/sshd_config 推荐配置: │
│ │
│ PermitRootLogin prohibit-password # 禁止 root 密码登录 │
│ PasswordAuthentication no # 禁止密码认证 │
│ PubkeyAuthentication yes # 启用密钥认证 │
│ Port 22 # 可改为非标准端口 │
│ │
│ 效果: 只能通过密钥登录,极大提升安全性 │
└─────────────────────────────────────────────────────────────────┘
3. 使用 SSH Config 简化连接
# ~/.ssh/config
Host myserver
HostName your-server-ip
User root
IdentityFile ~/.ssh/id_ed25519
# 可选:自动建立隧道
LocalForward 15432 127.0.0.1:5432
LocalForward 16379 127.0.0.1:6379
配置后只需:ssh myserver 即可自动建立所有隧道。
🚀 总结
核心价值
| 没有 SSH 隧道 | 有 SSH 隧道 |
|---|---|
| 🔓 数据库端口暴露 | 🔐 数据库端口隐藏 |
| 😰 可能被扫描攻击 | 😊 只暴露 SSH 端口 |
| 📡 数据明文传输 | 🔒 全程加密传输 |
| 🔑 需要数据库认证 | 🔑 SSH + 数据库双重认证 |
工作原理图解
你的电脑 云服务器
┌─────────────┐ ┌─────────────┐
│ │ │ │
│ Navicat │ │ PostgreSQL │
│ ↓ │ │ 127.0.0.1 │
│ 本地端口 │ │ :5432 │
│ ↓ │ SSH 隧道 │ ↑ │
│ SSH 客户端 │ =============> │ SSH 服务端 │
│ │ (加密) │ │
└─────────────┘ └─────────────┘
数据流: Navicat → 本地端口 → SSH加密 → 服务器SSH → 本地DB
记忆口诀
🔐 数据库只听 127,外面根本进不来
🚇 SSH 隧道是通道,加密传输最安全
🔑 密钥认证更可靠,私钥永远不外传
📍 记住视角很重要,127 是服务器的
SSH 隧道是连接远程数据库的最佳实践。
它让你既能方便地管理数据库,又不牺牲安全性。现在,你的数据库连接安全吗? 🛡️