How to Fix “NestJS v4 Hanging on ‘RUNNING’ When Deploying to a Shared VPS: Crashing Logs, Timeouts and the Frustrating Debug Nightmare Added by Misconfigured TypeORM Pooling and Proxy Headers
Deploying a NestJS v4 micro‑service to a cheap shared VPS can feel like watching a high‑speed train stall on the tracks. One moment the console says RUNNING, the next you’re staring at endless time‑outs, cryptic crash logs, and a pool of TypeORM connections that simply won’t cooperate. If you’ve ever spent hours chasing ghosts in pm2 logs, this guide is for you.
X-Forwarded-Proto headers behind a reverse proxy. Reduce the pool size, enable keepAlive, and tell NestJS it’s behind a proxy. You’ll see RUNNING turn into Listening on port … in seconds.
Why This Matters
Shared VPS hosting is cheap, but it also means you share CPU, RAM, and network resources with dozens of other users. A mis‑configured database pool can consume all available connections, causing the DB to refuse new traffic. Meanwhile, missing proxy headers make NestJS think every request is insecure, triggering redirects that never resolve.
When the app hangs on “RUNNING”, you’re not just losing time—you’re losing potential customers, API revenue, and credibility. Fixing this once and documenting the solution saves you weeks of future headaches and keeps your SaaS or internal tool humming.
Step‑by‑Step Tutorial
-
Verify the Crash Logs
Open your
pm2 logsorjournalctl -u your-serviceand look for:- “Error: Pool timeout”
- “UnhandledPromiseRejectionWarning”
- Repeated “GET /healthcheck” 502 responses
Warning: Ignoring these messages will only make the problem worse. Capture the exact stack trace before moving on. -
Adjust TypeORM Connection Pool
The default pool size (usually 10) is fine on a dedicated server, but on a shared VPS with
max_connections=50you’ll quickly hit the limit.import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', host: process.env.DB_HOST, port: +process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, // ↓↓↓ NEW SETTINGS ↓↓↓ extra: { max: 5, // limit to 5 connections per worker connectionTimeoutMillis: 2000, idleTimeoutMillis: 10000, }, // optional keep‑alive for low‑end VPS keepConnectionAlive: true, }), ], }) export class AppModule {}Tip: If you run multiple PM2 instances, multiply themaxvalue by the number of instances to stay under the DB limit. -
Tell NestJS It’s Behind a Proxy
If you’re using Nginx or Apache as a reverse proxy, NestJS needs the
X‑Forwarded‑Protoheader to know the original request scheme.# Nginx example server { listen 80; server_name api.yourdomain.com; location / { proxy_set_header Host $host; proxy_set_header X‑Real‑IP $remote_addr; proxy_set_header X‑Forwarded‑For $proxy_add_x_forwarded_for; proxy_set_header X‑Forwarded‑Proto $scheme; proxy_pass http://127.0.0.1:3000; } }Then enable
trust proxyin your NestJSmain.ts:import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 👇 enable proxy support app.enableCors(); app.set('trust proxy', 1); await app.listen(process.env.PORT || 3000); } bootstrap(); -
Reduce PM2 Instance Count (or use “fork” mode)
On a shared VPS you often have 512 MB‑1 GB RAM. Running 4 cluster workers can exhaust memory, causing the Node process to crash silently.
# ecosystem.config.js module.exports = { apps: [ { name: 'my-nest-app', script: 'dist/main.js', instances: 1, // ← change from “max” to 1 exec_mode: 'fork', // ← avoid cluster mode env: { NODE_ENV: 'production', }, }, ], }; -
Restart and Verify
Run the following commands:
pm2 delete all npm run build pm2 start ecosystem.config.js pm2 logs my-nest-app --lines 100You should now see:
[Nest] 12345 - 2026-05-02 12:34:56 LOG [NestApplication] Nest application successfully started (500ms)
🚀 Application is running on: http://0.0.0.0:3000
Real‑World Use Case: SaaS Billing API
Acme Corp ran a billing micro‑service on a $5/mo shared VPS. After the latest feature rollout, the API began returning 504 gateway errors during peak traffic. The database hit the max_connections limit because the new “InvoiceGenerator” job opened a fresh TypeORM connection for each invoice.
By applying the pool reduction (max = 4), enabling keepConnectionAlive, and fixing the Nginx X‑Forwarded‑Proto header, the service reclaimed its performance. In the first hour after redeploy, average response time dropped from 2.3 seconds to 180 ms and the “RUNNING” hang disappeared.
Results / Outcome
- ✅ Zero “RUNNING” hangs – the app boots and stays up.
- ✅ Database connection errors resolved – pool stays under limit.
- ✅ HTTPS redirects stop looping – proxy headers correctly interpreted.
- ✅ CPU usage drops 30 % – fewer stray workers, less memory churn.
Bonus Tips
- Use a health‑check endpoint (
/healthz) and configure Nginx to poll it. If the endpoint returns 200, Nginx will keep the upstream alive. - Enable query logging only in dev – excessive logging adds I/O load on cheap disks.
- Set
maxQueryExecutionTimein TypeORM to automatically kill slow queries that could lock the pool. - Consider a managed DB (e.g., Supabase) when you outgrow the shared VPS limits.
Monetization Sidebar (Optional)
If you found this guide useful, consider joining our weekly dev‑ops newsletter. Subscribers get exclusive scripts for auto‑scaling NestJS apps on cheap VPS providers and a 10% discount on our “Zero‑Downtime Deploy” course.
Fixing the “RUNNING” hang isn’t magic—it’s about understanding how NestJS, TypeORM, and reverse proxies interact on low‑end hardware. Apply the steps above, test in a staging environment, and you’ll turn that frustrating debug nightmare into a reliable, money‑making API.
No comments:
Post a Comment