ssh -L 命令必须显式绑定 127.0.0.1,否则隧道可能监听在 ::1 或随机接口导致失效;正确写法为 ssh -L 127.0.0.1:3307:127.0.0.1:3306 user@remote-server,且需验证 ss -tln | grep :3307 确认监听成功。

ssh -L 命令必须显式绑定 127.0.0.1,否则隧道不生效
很多人在本地开隧道时写 ssh -L 3307:127.0.0.1:3306 user@remote,看似简洁,实则危险。某些系统(如 macOS 或新版 OpenSSH)会默认把本地端口绑在 ::1(IPv6 回环),而 phpMyAdmin 的 PHP MySQL 扩展在连接 localhost 时可能走 Unix socket,根本不会发 TCP 请求——结果就是隧道进程明明在跑,但连不上。
正确写法必须前后都写死 IPv4 地址:ssh -L 127.0.0.1:3307:127.0.0.1:3306 user@remote-server。验证是否生效:执行 ss -tln | grep :3307,看到 127.0.0.1:3307 且 State 是 LISTEN 才算真正监听成功。
- 别用
0.0.0.0:3307,这会让端口暴露给局域网,phpMyAdmin 可能因 Host 头校验失败拒绝登录 - 每个远程服务器需独立端口,比如第二台用
127.0.0.1:3308,避免端口冲突 - 隧道断开后不会自动重连,建议用
autossh替代原生命令,例如:autossh -M 0 -N -L 127.0.0.1:3307:127.0.0.1:3306 user@server1
config.inc.php 中每个 $cfg['Servers'][$i] 必须严格对应一个隧道端口
phpMyAdmin 5.2 不再支持旧版的 $i=0; $i++ 手动递增写法,索引错位会导致部分服务器配置被跳过或覆盖。官方推荐结构是先定义主机数组,再循环生成配置块——这是唯一能稳定显示多个服务器下拉选项的方式。
示例片段(直接追加到 config.inc.php 底部):
立即学习“PHP免费学习笔记(深入)”;
$servers = [
['host' => '127.0.0.1', 'port' => '3307', 'user' => 'root', 'password' => 'pass1'],
['host' => '127.0.0.1', 'port' => '3308', 'user' => 'admin', 'password' => 'pass2'],
];
<p>$i = 0;
foreach ($servers as $server) {
$i++;
$cfg['Servers'][$i]['auth_type'] = 'cookie';
$cfg['Servers'][$i]['host'] = $server['host'];
$cfg['Servers'][$i]['port'] = $server['port'];
$cfg['Servers'][$i]['user'] = $server['user'];
$cfg['Servers'][$i]['password'] = $server['password'];
$cfg['Servers'][$i]['connect_type'] = 'tcp'; // 强制走 TCP,绕过 socket
}- 索引
$i必须从 1 开始,且不能重复;$cfg['Servers'][0]会被 phpMyAdmin 忽略 -
$cfg['Servers'][$i]['host']一定填'127.0.0.1',不是'localhost',否则 PHP 会尝试 Unix socket 连接,绕过你的隧道 - 如果某台远程 MySQL 只监听
127.0.0.1:3306(常见于 Docker 容器),它必须允许user@'127.0.0.1'登录,光有user@'%'不够
phpMyAdmin 报错 #2002 时,优先查隧道状态而非改配置
错误信息 #2002 Cannot log in to the MySQL server 在多隧道场景下,90% 是因为某个隧道进程已退出,但 phpMyAdmin 配置仍指向那个端口。它不会告诉你“端口没监听”,只会笼统报连不上。
排查顺序必须是:
- 运行
ps aux | grep 'ssh -L',确认所有隧道命令都在进程列表里;没有就立刻重启 - 对每个端口单独测试:
mysql -h 127.0.0.1 -P 3307 -u root -p和mysql -h 127.0.0.1 -P 3308 -u admin -p—— 注意必须用127.0.0.1,localhost会走 socket - 打开浏览器开发者工具 → Network 标签页,点登录按钮,看请求 URL 是发往
/index.php?route=/还是带了server=1或server=2参数;如果始终是server=1,说明下拉选择没生效,检查配置中是否漏了$cfg['Servers'][$i]['auth_type']
MySQL 服务端必须允许 127.0.0.1 的本地回环访问
隧道只是转发流量,最终请求还是落到远程 MySQL 实例上。如果那台机器的 MySQL 配置了 bind-address = 127.0.0.1(安全常见做法),它只接受来自本机的连接——但关键点在于:它必须允许用户从 127.0.0.1 登录,而不仅仅是 localhost。
登录远程服务器执行:
mysql -u root -p -e "SELECT host,user FROM mysql.user WHERE user='root';"
如果结果里只有 localhost 行,没有 127.0.0.1 行,就得补一条:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES;
- 别用
GRANT ... TO 'root'@'%'替代,这等于开放所有 IP,违背内网隧道初衷 - Docker 环境尤其要注意:容器内 MySQL 默认 bind-address 是
127.0.0.1,但宿主机 ssh 隧道发来的请求对容器来说源 IP 就是127.0.0.1,所以权限必须明确包含该 host - 修改完记得重启 MySQL 服务,
systemctl restart mysql或docker restart mysql-container
复杂点在于:你得同时维护三处状态——本地的 SSH 进程、phpMyAdmin 的 PHP 配置数组索引、远程 MySQL 的用户 host 白名单。任何一处脱节,都会表现为“选了服务器却连不上”,而且错误提示完全不指明具体环节。



















