DEV Community

Vpsfordev
Vpsfordev

Posted on • Originally published at vpsfor.dev

How many Node.js apps can you run on a 2-core 1GB VPS with PM2? (The math)

Every indie hacker running on a $5 VPS has asked this question. The answer depends entirely on what kind of apps you're running — and most guides skip the actual math.

Here's the breakdown I wish I had when I started.

Your real available RAM is not 1GB

The OS and PM2 daemon eat RAM before your app runs a single line of code:

Component RAM Used
Ubuntu 22.04 (minimal) ~180MB
PM2 daemon ~30MB
Available for your apps ~790MB

So you're working with ~790MB, not 1024MB. Now let's see what fits.

App capacity by type

App Type Idle RAM Under Load Max Apps on 2-core 1GB
Simple bot / webhook 60–90MB ~120MB 6–8 apps (fork mode)
Standard REST API (Express/Fastify) 80–120MB ~200MB 2–3 apps
Next.js / Nuxt SSR 350–500MB 600MB+ 1 app only
Heavy AI / Discord bot 600MB+ OOM risk 1 app + swap

The "under load" column is what actually matters — idle numbers are misleading because Node.js apps spike heavily during deploys, GC cycles, and traffic bursts.

Option A: One API, full cluster mode (2 workers)

If you have a single Node.js API and want to use both CPU cores:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-api',
    script: './dist/index.js',
    instances: 2,             // exactly 2 workers for a 2-core VPS
    exec_mode: 'cluster',
    max_memory_restart: '350M',
    node_args: '--max-old-space-size=320',
    env_production: { NODE_ENV: 'production', PORT: 3000 }
  }]
};
Enter fullscreen mode Exit fullscreen mode

2 workers × 350MB limit = 700MB max, leaving ~90MB headroom. The --max-old-space-size flag is critical — without it, V8 can claim up to 1.5GB and OOM-kill your server.

Option B: Multiple small apps, fork mode

If you're hosting 2–3 separate projects on the same VPS:

module.exports = {
  apps: [
    {
      name: 'api',
      script: './api/dist/index.js',
      instances: 1,
      exec_mode: 'fork',
      max_memory_restart: '250M',
      node_args: '--max-old-space-size=200',
    },
    {
      name: 'worker',
      script: './worker/dist/index.js',
      instances: 1,
      exec_mode: 'fork',
      max_memory_restart: '200M',
      node_args: '--max-old-space-size=160',
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Two apps × ~200MB = 400MB used, ~390MB free for spikes. Fork mode is better here than cluster — each app runs independently, and a crash in one doesn't affect the other.

3 things I learned the hard way

1. Always create a swap file — it's not optional on 1GB

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Enter fullscreen mode Exit fullscreen mode

Deploys temporarily spike RAM usage by 30–50%. Without swap, a npm install on a busy server will OOM-kill your running app.

2. max_memory_restart is a safety net, not a target

If PM2 is regularly hitting the limit and restarting your app, that's a memory leak — fix the code, don't raise the limit. Use pm2 monit to watch RSS (resident set size) over time.

3. Node.js v20+ saves you ~20% RAM for free

Modern V8 engines use pointer compression by default on 64-bit systems, reducing heap object size significantly. If you're still on Node 16/18, upgrading alone might give you room for one more small app.

The quick decision guide

  • 1 production API → cluster mode, 2 instances, max_memory_restart: '350M'
  • 2 small projects → fork mode, 1 instance each, max_memory_restart: '250M'
  • 3+ bots/webhooks → fork mode, max_memory_restart: '120M', add swap
  • Next.js SSR → 1 app only, upgrade to 2GB RAM if possible

Full PM2 ecosystem configs for 1GB, 2GB, and 4GB VPS tiers at vpsfor.dev →

Top comments (0)