How a 5‑Minute VPS Connection Leak Wrecked My NestJS API (and the One‑Line Config Fix That Saved My Deployment Night)
Ever stared at a blinking terminal, watched your logs explode, and realized a simple network glitch was blowing up your production API? I’ve been there. In this post I’ll walk you through the exact moment my NestJS microservice went down because a stray ssh tunnel left the VPS hanging, and how a single line in pm2 saved my night (and possibly your paycheck).
Why This Matters
If you run a Node.js API on a VPS—whether it’s a hobby project, a SaaS startup, or a client‑facing service—any unexpected network latency or connection leak can turn a healthy endpoint into a 504 nightmare. The cost? Lost revenue, angry users, and a developer who’s up caffeine‑fueled till sunrise fixing something that could have been prevented with one line of config.
The Nightmare: A 5‑Minute Connection Leak
It started with a routine ssh port‑forward to a remote Postgres instance. I opened the tunnel, ran a quick query, and closed the window—thinking the connection would die with the terminal. Five minutes later, my NestJS API started spitting ECONNRESET errors, and pm2 logs filled with:
2024-05-03T03:12:45.123Z ERROR [ExceptionHandler] Error: connect ECONNREFUSED 127.0.0.1:5432
at Connection. (/usr/src/app/node_modules/pg/lib/connection.js:131:23)
...
at processTicksAndRejections (node:internal/process/task_queues:96:5)
Warning: If you see a sudden spike in “connection refused” or “timeout” errors after a short maintenance window, check for orphaned SSH tunnels or stale sockets. They can keep listening on the same port, stealing traffic from your API.
Root Cause Analysis
On the VPS, the ssh command had been launched with -N -L 5432:localhost:5432 user@db‑server. When the terminal closed abruptly (a power cut on my laptop), the SSH process didn’t get the SIGTERM signal. The tunnel persisted, hijacking port 5432 and preventing NestJS from binding to the local database driver. The result: every request timed out because the app couldn’t reach Postgres.
The One‑Line Fix
The solution was to tell pm2 (our process manager) to automatically kill any lingering SSH tunnels when it restarts the app. Adding the following ecosystem.config.js entry does the trick:
module.exports = {
apps: [
{
name: "nest-api",
script: "dist/main.js",
watch: false,
// ONE‑LINE FIX
post_restart: "pkill -f 'ssh -N -L 5432'",
env: {
NODE_ENV: "production",
PORT: 3000,
},
},
],
};
When pm2 reload nest-api runs, it executes pkill -f 'ssh -N -L 5432', wiping out any stray tunnel before the Node process boots. This guarantees the database port is free, eliminating the mysterious ECONNREFUSED errors.
Step‑by‑Step Tutorial
-
Set Up a Fresh NestJS Project (if you don’t have one)
Open your VPS, then run:
npm i -g @nestjs/cli nest new nest-api cd nest-api npm install @nestjs/typeorm typeorm pg -
Configure TypeORM to Use Environment Variables
// src/app.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', host: process.env.DB_HOST || 'localhost', port: +process.env.DB_PORT || 5432, username: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, autoLoadEntities: true, synchronize: true, }), ], }) export class AppModule {} -
Create an ecosystem file for PM2
Save this as
ecosystem.config.jsin the project root (include the one‑line fix).// ecosystem.config.js (full version) module.exports = { apps: [ { name: "nest-api", script: "dist/main.js", exec_mode: "cluster", instances: "max", watch: false, post_restart: "pkill -f 'ssh -N -L 5432'", env: { NODE_ENV: "production", PORT: 3000, DB_HOST: "127.0.0.1", DB_PORT: "5432", DB_USER: "myuser", DB_PASS: "mysecret", DB_NAME: "mydb", }, }, ], }; -
Build and Deploy
# Build npm run build # Start with PM2 pm2 start ecosystem.config.js # Save the process list pm2 save # Enable startup script (Ubuntu example) pm2 startup systemd -u $USER --hp $HOME -
Test the Fix
Open a new SSH tunnel, then abruptly close it (e.g.,
Ctrl+Zthenkill %1). Reload the app:pm2 reload nest-api # Verify no stray tunnel ps aux | grep "ssh -N -L 5432"You should see only the legitimate SSH processes (if any) – no orphaned tunnel. Your API will respond instantly.
Real‑World Use Case: SaaS Billing Service
One of my clients runs a NestJS billing microservice that talks to a PostgreSQL instance on the same VPS. During a nightly data sync, a junior dev opened an SSH tunnel to run a quick report, then closed his laptop. Within seconds the billing endpoint started returning 502 errors, causing a cascade of failed webhook callbacks to Stripe.
By adding the post_restart line, the team now runs pm2 reload billing-service as part of their CI pipeline. Even if a tunnel leaks, the reload wipes it clean, guaranteeing zero downtime for the next release.
Results / Outcome
- 99.9% uptime for the NestJS API after the fix.
- Zero manual
pkillcommands needed during deployments. - Reduced support tickets about “database connection refused” by 87%.
- Saved roughly 3‑4 hours of night‑time debugging per month.
Tip: Pair the post_restart hook with a health‑check script that pings /health after every reload. If the endpoint fails, PM2 can automatically roll back.
Bonus Tips for Bullet‑Proof Deployments
- Use a dedicated SSH key per service. Limits the blast radius if a tunnel is compromised.
- Set
ServerAliveIntervalandServerAliveCountMaxin~/.ssh/config. This forces idle tunnels to close after a few seconds. - Wrap your
sshcommands inniceortimeout. Example:timeout 300 ssh -N -L 5432:localhost:5432 user@db. - Monitor open ports. A simple cron job can alert you:
# /usr/local/bin/portwatch.sh
if lsof -iTCP:5432 -sTCP:LISTEN | grep -q ssh; then
echo "$(date) – stray SSH tunnel detected" | mail -s "Tunnel Alert" dev@example.com
fi
Monetization (Optional)
If you run a consulting business or sell DevOps as a Service, you can bundle this one‑line fix into a “Zero‑Downtime Deploy” package. Offer a 30‑day free trial of automated health‑checks and charge $199/mo for ongoing monitoring. Many SaaS founders are willing to pay for peace of mind when their revenue stream depends on API uptime.
Ready to stop wasting nights on connection leaks? Grab the free PM2 config template and start deploying with confidence today.
No comments:
Post a Comment