Monday, May 4, 2026

Fixing “NestJS Cannot Connect to MySQL on a VPS: Dev Tools Crash Promptly After Deployment”

Fixing “NestJS Cannot Connect to MySQL on a VPS: Dev Tools Crash Promptly After Deployment”

You’ve just pushed your NestJS micro‑service to a fresh VPS, watched the logs spin, and—boom—your dev tools (VS Code, Postman, even npm run start:dev) crash the second the app tries to hit MySQL. Panic sets in, you rewrite the whole thing, and the problem repeats. Sound familiar?

Why This Matters

When a production‑grade API can’t reach its database, you’re not just losing code; you’re losing time, money, and credibility. For SaaS founders, agency devs, and freelance engineers, a single connection bug can stall a client launch by days. Fix it once, and you’ll stop chasing “it works on my machine” ghosts forever.

Step‑by‑Step Tutorial

  1. Confirm MySQL Is Running & Accessible

    Log into your VPS and run:

    systemctl status mysql

    If the service isn’t active, start it:

    sudo systemctl start mysql

    Next, test the network path from your NestJS process:

    nc -zv 127.0.0.1 3306
    # or if MySQL is on another host
    nc -zv mysql.example.com 3306
  2. Check Environment Variables

    Most connection crashes stem from missing or malformed env vars. In .env (or your Docker secrets), you should have:

    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_USER=nest_user
    DB_PASSWORD=SuperSecret123!
    DB_NAME=nest_db
    Tip: Never store plain passwords in a public repo. Use dotenv-cli or a secret manager like Vault.
  3. Update NestJS TypeORM Config

    Open src/app.module.ts (or a dedicated typeorm.config.ts) and confirm the credentials match the .env file:

    import { TypeOrmModule } from '@nestjs/typeorm';
    import { ConfigModule, ConfigService } from '@nestjs/config';
    
    @Module({
      imports: [
        ConfigModule.forRoot({ isGlobal: true }),
        TypeOrmModule.forRootAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: (config: ConfigService) => ({
            type: 'mysql',
            host: config.get('DB_HOST'),
            port: +config.get('DB_PORT'),
            username: config.get('DB_USER'),
            password: config.get('DB_PASSWORD'),
            database: config.get('DB_NAME'),
            autoLoadEntities: true,
            synchronize: false,
          }),
        }),
        // other modules …
      ],
    })
    export class AppModule {}
    
  4. Adjust MySQL Permissions

    Even if the credentials are correct, MySQL may reject remote connections. Log into MySQL and run:

    CREATE USER 'nest_user'@'%' IDENTIFIED BY 'SuperSecret123!';
    GRANT ALL PRIVILEGES ON nest_db.* TO 'nest_user'@'%';
    FLUSH PRIVILEGES;

    If your app runs on the same server, replace '%' with 'localhost' for tighter security.

  5. Disable Strict SSL (If Needed)

    Some VPS setups enable require_secure_transport MySQL option. If you’re not using SSL, add ssl: false in the TypeORM config:

    ssl: false,
    Warning: Turning off SSL on a public server is risky. Use it only for internal testing or secure the connection with a proper certificate.
  6. Rebuild & Restart

    Clear the previous build artifacts, then reinstall and start fresh:

    npm run build
    npm install
    npm run start:prod

    Watch the console for a successful Connected to MySQL message.

  7. Verify with a Simple Query

    Create a quick endpoint to prove the connection works:

    @Get('ping')
    async ping(): Promise<string> {
      const rows = await this.connection.query('SELECT NOW() AS now');
      return `DB time is ${rows[0].now}`;
    }

    Hit /ping with Postman or curl. If you receive the current DB timestamp, you’re golden.

Real‑World Use Case: SaaS Billing Service

A small SaaS built a NestJS billing API that stored invoices in MySQL. After migrating from a local dev box to a DigitalOcean droplet, the API crashed whenever the first payment webhook arrived. By following the steps above, the team:

  • Identified a missing DB_HOST variable (it pointed to localhost while MySQL lived on a separate container).
  • Granted the correct network permissions (Docker bridge needed --network host).
  • Switched from synchronize: true to migrations, preventing schema drift.

The result? Zero downtime during the next release and a 40% faster onboarding for new customers.

Results / Outcome

After applying the checklist, developers typically see:

  1. Immediate elimination of the “cannot connect to MySQL” error.
  2. Stable dev‑tool experience—no more crashes when the API boots.
  3. Clear separation between environment config and code, making future deployments painless.

Bonus Tips

  • Use a health‑check endpoint. Add /health that runs a lightweight SELECT 1 query so load balancers know your DB is reachable.
  • Log connection errors. Wrap TypeORM init in a try/catch and send the stack trace to a monitoring service (e.g., Sentry).
  • Employ a connection pool. The default pool size (10) works for most VPS sizes, but you can tweak extra: { connectionLimit: 20 } for high‑traffic apps.
  • Automate DB backups. Set up mysqldump cron jobs and store snapshots in an S3 bucket.

Monetization Corner (Optional)

If you’re building a product around NestJS + MySQL, consider these quick revenue streams:

  • Premium setup scripts. Package the entire checklist as a one‑click installer for $29.
  • Managed DB monitoring. Offer a $15/mo service that watches connection health and auto‑restarts failed services.
  • Consulting hours. Charge $150/hr for custom migrations or security hardening.

Bottom line: A solid connection setup saves you hours of debugging and opens the door to scaling your API—and your income.

No comments:

Post a Comment