必须正确设置remote_bind_address为MongoDB实际监听地址,如127.0.0.1、内网IP或云数据库域名端口;Python中MongoClient需连接127.0.0.1:server.local_bind_port,且必须显式调用server.stop()释放隧道。

ssh -L 命令必须写对 remote_bind_address,不能填错 MongoDB 实际监听地址
很多人用 ssh -L27017:localhost:27017 user@jump-host 连不上,核心原因是远程 MongoDB 并不监听 localhost:27017——它可能绑在 127.0.0.1(没问题),也可能绑在内网 IP(如 192.168.1.100:27017),甚至云数据库(如阿里云 dds-xxx.mongodb.rds.aliyuncs.com:3717)根本不在跳板机本机。
关键判断点:
- 如果 MongoDB 和 SSH 跳板机是同一台机器 →
remote_bind_address = ('127.0.0.1', 27017)(或localhost,但推荐显式写127.0.0.1) - 如果 MongoDB 在跳板机同局域网的另一台机器上 →
remote_bind_address = ('192.168.1.100', 27017),且跳板机必须能telnet 192.168.1.100 27017 - 如果是阿里云/腾讯云 MongoDB(即
xxx.mongodb.rds.xxx.com)→remote_bind_address必须填这个域名和端口(如('dds-uf66d90a9c38f2a41756-pub.mongodb.rds.aliyuncs.com', 3717)),不能填跳板机 IP
错误示例:ssh -L27017:10.0.0.5:27017 user@jump-host,但跳板机根本无法访问 10.0.0.5 → 隧道建立成功,但一连就超时。
Python 中 MongoClient 的 host 必须是 127.0.0.1,端口必须读 server.local_bind_port
用 sshtunnel.SSHTunnelForwarder 启动隧道后,server.local_bind_port 是唯一可信端口。硬写 27017 或从命令行参数里“猜”端口,99% 会连错。
正确姿势:
server = SSHTunnelForwarder(...)server.start()port = server.local_bind_port # 不是 27017,不是 server.local_port,就是它client = pymongo.MongoClient(f'mongodb://user:pass@127.0.0.1:{port}/db?authMechanism=SCRAM-SHA-256')
常见坑:
- URI 中 host 写成跳板机 IP、MongoDB 域名或
localhost(Windows 下localhost可能解析为 ::1)→ 改用127.0.0.1 - 漏写
?authMechanism=SCRAM-SHA-256→ MongoDB 4.0+ 默认拒绝旧机制,报Authentication failed - 把云数据库连接串(如
mongodb+srv://...)直接塞进 URI → 不支持隧道,必须降级为普通mongodb://+ 显式 host/port
私钥权限和路径写法在不同系统上差异极大
ssh_pkey 参数传的是文件路径,不是内容,且系统对权限和路径格式极其敏感。
Linux/macOS:
- 私钥文件权限必须是
600(chmod 600 ~/.ssh/id_ed25519),否则 SSH 拒绝加载,报Permissions for 'xxx' are too open - 路径用正斜杠,支持波浪线:
~/.ssh/id_ed25519
Windows:
- 反斜杠
\在 Python 字符串里要双写或用原始字符串:r'C:\Users\me\.ssh\id_ed25519',否则\n被当换行 - OpenSSH for Windows 默认不校验私钥权限,但某些版本仍会失败,建议统一设为
600(用 PowerShell:icacls "C:\path\key" /reset /inheritance:r /grant:r "$env:USERNAME:(R)")
通用建议:用 pathlib.Path 构造路径,避免手拼;启动前先用 ssh -i key_path user@host 手动验证私钥是否可用。
程序退出必须 stop 隧道,否则下次运行卡死或端口被占
SSHTunnelForwarder 启动后会 fork 一个后台 SSH 进程。如果 Python 异常退出、Ctrl+C 中断、或没调 server.stop(),这个进程不会自动清理。
后果:
- 下次运行
server.start()卡住,或报Address already in use - 大量僵尸 SSH 进程堆积,消耗跳板机资源
可靠做法只有两种:
- 用
try/finally包裹(尤其含多次 DB 操作时):server = SSHTunnelForwarder(...)<br>server.start()<br>try:<br> client = MongoClient(...)<br> # do work<br>finally:<br> server.stop()
- 模块级变量 +
atexit.register(server.stop),但需确保server不是函数局部变量(否则引用失效)
别依赖脚本自然退出——网络超时、SIGTERM、Jupyter kernel restart 都可能绕过正常流程。隧道生命周期必须显式管理。











