You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js应用通过SSH隧道无法连接NameCheap远程MySQL数据库求助

Node.js连接NameCheap远程MySQL数据库的问题排查与优化方案

问题背景

本地Node.js应用尝试连接NameCheap托管的远程MySQL数据库,两种连接方式均失败:

  • 直接连接:使用mysql包连接被拒绝,推测是数据库远程访问限制导致。
  • SSH隧道连接:使用tunnel-ssh包搭建隧道后仍连接失败,运行时隧道未正确建立,MySQL连接超时或被拒绝。
  • 已确认信息:联系NameCheap支持验证了SSH凭证和MySQL信息的正确性,但无法提供Node.js相关的进一步帮助;本地可使用相同SSH凭证正常登录NameCheap服务器,计划后续将应用部署后使用该在线数据库。

核心疑问

  1. 为何搭建SSH隧道后仍无法连接?
  2. 连接NameCheap远程MySQL时可能遗漏哪些配置?
  3. 是否有更优的Node.js通过SSH隧道连接远程MySQL的方案?

一、隧道连接失败的原因排查

1. 代码中的明显错误

你的代码存在两处关键问题:

  • serverOptions对象中host和port之间缺少逗号,导致语法错误:
    const serverOptions: ServerOptions = {
      host: '127.0.0.1' // 此处遗漏逗号
      port: 3307,
    };
    
  • Sequelize连接端口与隧道本地监听端口不匹配:隧道监听本地3307端口,但Sequelize配置连接的是3306端口,请求无法通过隧道转发。

2. 连接时机顺序错误

代码先初始化Sequelize实例,再建立SSH隧道,可能在隧道未完全就绪时就发起数据库连接,导致超时失败。

3. MySQL用户权限限制

NameCheap的MySQL默认仅允许localhost或服务器内部IP访问,需确认你的MySQL用户拥有username@localhost的访问权限(隧道转发后,服务器端MySQL接收到的是本地请求)。


二、可能遗漏的配置项

  • MySQL权限配置:登录NameCheap MySQL管理面板,确认数据库用户拥有访问目标数据库的权限,且用户主机范围包含localhost。
  • 服务器内部端口访问:确认NameCheap服务器上的MySQL端口(默认3306)允许本地进程访问,无防火墙规则拦截。
  • 环境变量校验:再次核对.env文件中DB_HOST(通常为localhost,因为NameCheap MySQL一般与Web服务器同机)、DB_PORT(默认3306)、SSH_HOST(服务器IP)、SSH_PORT(通常为22)的正确性。
  • 依赖版本兼容性:确认tunnel-ssh版本与代码API匹配,不同版本的参数结构可能存在差异。

三、优化后的SSH隧道连接方案

方案1:修正现有tunnel-ssh代码

修复语法错误、端口不匹配问题,调整连接时机:

import { Sequelize } from 'sequelize';
import { config } from 'dotenv';
import { createTunnel } from 'tunnel-ssh';
import mysql2 from 'mysql2';

config();

const isProduction = process.env.NODE_ENV === 'production';

let sequelize: Sequelize | null = null;

if (isProduction) {
  const tunnelConfig = {
    autoClose: true,
    ssh: {
      host: process.env.SSH_HOST!,
      port: Number(process.env.SSH_PORT) || 22,
      username: process.env.SSH_USER!,
      password: process.env.SSH_PASSWORD!
    },
    server: {
      host: '127.0.0.1',
      port: 3307 // 本地监听端口
    },
    forward: {
      srcAddr: '127.0.0.1',
      srcPort: 3307,
      dstAddr: process.env.DB_HOST || '127.0.0.1',
      dstPort: Number(process.env.DB_PORT) || 3306
    }
  };

  createTunnel(tunnelConfig)
    .then(() => {
      console.log('SSH隧道已建立');
      // 隧道就绪后初始化Sequelize
      sequelize = new Sequelize(
        process.env.DB_NAME!,
        process.env.DB_USER!,
        process.env.DB_PASSWORD!,
        {
          host: '127.0.0.1',
          port: 3307, // 与隧道本地端口一致
          dialect: 'mysql',
          dialectModule: mysql2,
          logging: false,
        }
      );

      sequelize.authenticate()
        .then(() => console.log('数据库连接成功'))
        .catch(dbErr => console.error('数据库连接失败:', dbErr));
    })
    .catch(tunnelErr => console.error('SSH隧道建立失败:', tunnelErr));
} else {
  sequelize = new Sequelize(
    process.env.DB_NAME!,
    process.env.DB_USER!,
    process.env.DB_PASSWORD!,
    {
      host: process.env.DB_HOST,
      port: Number(process.env.DB_PORT) || 3306,
      dialect: 'mysql',
      dialectModule: mysql2,
      logging: false,
    }
  );

  sequelize.authenticate()
    .then(() => console.log('本地数据库连接成功'))
    .catch(err => console.error('本地数据库连接失败:', err));
}

export default sequelize;

方案2:使用ssh2手动搭建隧道(更灵活)

直接使用ssh2库实现隧道转发,支持更精细的控制:

import { Sequelize } from 'sequelize';
import { config } from 'dotenv';
import { Client } from 'ssh2';
import mysql2 from 'mysql2';

config();

const isProduction = process.env.NODE_ENV === 'production';

let sequelize: Sequelize | null = null;

if (isProduction) {
  const sshClient = new Client();

  sshClient.on('ready', () => {
    console.log('SSH连接就绪');
    sshClient.forwardOut(
      '127.0.0.1',
      3307,
      process.env.DB_HOST || '127.0.0.1',
      Number(process.env.DB_PORT) || 3306,
      (err, stream) => {
        if (err) {
          console.error('端口转发失败:', err);
          return sshClient.end();
        }

        sequelize = new Sequelize(
          process.env.DB_NAME!,
          process.env.DB_USER!,
          process.env.DB_PASSWORD!,
          {
            host: '127.0.0.1',
            port: 3307,
            dialect: 'mysql',
            dialectModule: mysql2,
            logging: false,
            dialectOptions: {
              stream: stream // 传入SSH转发流
            }
          }
        );

        sequelize.authenticate()
          .then(() => console.log('数据库连接成功'))
          .catch(dbErr => console.error('数据库连接失败:', dbErr));
      }
    );
  });

  sshClient.connect({
    host: process.env.SSH_HOST!,
    port: Number(process.env.SSH_PORT) || 22,
    username: process.env.SSH_USER!,
    password: process.env.SSH_PASSWORD!
  });

  sshClient.on('error', (err) => {
    console.error('SSH连接错误:', err);
  });
} else {
  // 本地连接代码与之前一致
  sequelize = new Sequelize(
    process.env.DB_NAME!,
    process.env.DB_USER!,
    process.env.DB_PASSWORD!,
    {
      host: process.env.DB_HOST,
      port: Number(process.env.DB_PORT) || 3306,
      dialect: 'mysql',
      dialectModule: mysql2,
      logging: false,
    }
  );

  sequelize.authenticate()
    .then(() => console.log('本地数据库连接成功'))
    .catch(err => console.error('本地数据库连接失败:', err));
}

export default sequelize;

方案3:部署后的最优方案

若后续将Node.js应用部署到NameCheap的同一台服务器上,无需SSH隧道,直接使用localhost连接MySQL即可,效率更高。


内容的提问来源于stack exchange,提问作者GameOwner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:04:56