Node.js Production VPS Setup: PM2 Cluster Mode, Nginx Reverse Proxy and Hardening

The steps that turn a Node.js app into a production service on a VPS.

A production Node.js service on a VPS needs five things: the app running with NODE_ENV=production, a process manager that restarts it and uses every core, a reverse proxy that terminates TLS, a firewall that only exposes the proxy, and logs you can read. This guide sets that up with PM2 and Nginx on a KVM VPS.

Prerequisites

  • A KVM VPS with root access, such as the SD-2Core-4GB plan (₹596 per month) for a small API or SD-4Core-8GB (₹1,438) for a busier service, from the StreamData KVM VPS plans.
  • A current long-term support release of Node.js.
  • A domain name pointing at the server.

Step 1: run as a non-root user

adduser app
su - app
git clone <your-repo> ~/service && cd ~/service
npm ci --omit=dev

Step 2: set the environment

The Node.js documentation explains that many libraries behave differently when NODE_ENV=production, for example by caching templates and reducing logging. Keep secrets in an environment file readable only by the app user, not in the repository.

Step 3: PM2 in cluster mode

Node.js runs JavaScript on one thread per process. The built-in cluster module, and PM2's cluster mode on top of it, start one process per core and share the listening port.

npm install -g pm2
pm2 start server.js -i max --name api
pm2 save
pm2 startup systemd

Cluster mode only works if the app is stateless between requests. Keep sessions in Redis or the database, not in process memory.

Step 4: Nginx as the reverse proxy

server {
  listen 443 ssl;
  server_name api.example.com;
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

The Nginx proxy module documentation covers timeouts and buffering. For WebSockets, add the Upgrade and Connection headers.

Step 5: firewall

ufw allow OpenSSH
ufw allow 80,443/tcp
ufw enable

Bind the Node.js process to 127.0.0.1 so only Nginx can reach it.

Step 6: logs and monitoring

  • pm2 logs for application output, with log rotation enabled
  • Nginx access and error logs for request-level problems
  • Memory per process in pm2 monit. A slow steady rise usually means a leak

Verify

  1. Kill one worker with pm2 stop 0 and confirm requests still succeed.
  2. Reboot the VPS and confirm the service comes back on its own.
  3. Confirm port 3000 is not reachable from outside.

Sizing and scaling

CPU-bound Node.js services benefit from faster cores. If profiling shows the event loop is busy with computation rather than waiting on I/O, the 9950X range is worth testing:

9950X planvCPUDDR5NVMeMonthly
9950X VPS 16416 GB64 GB₹2,399
9950X VPS 32632 GB128 GB₹4,799
9950X VPS 48848 GB192 GB₹7,199
9950X VPS 641064 GB256 GB₹9,599

The AMD Ryzen 9 9950X is a 16-core, 32-thread Zen 5 processor rated for boost clocks up to 5.7 GHz. A 9950X VPS receives a virtual allocation on that host, not exclusive use of all sixteen cores. See the 9950X VPS plans page.

Protection

StreamData positions eligible services behind Cloudflare (Magic Transit-ready) and StormWall partner mitigation paths, chosen by route, workload and location, with a Layer 3 and 4 focus. The DDoS protection page is explicit that no provider can guarantee 100% protection, and that an attacked IP may be temporarily null-routed when traffic exceeds safe levels. Layer 7 abuse still needs rate limits, caching and application controls on your side.

Zero-downtime deploys

With PM2 in cluster mode, pm2 reload api restarts workers one at a time so the service keeps answering during a deploy. Pair it with a health-check endpoint that the proxy or your monitoring can call, and make the app handle SIGINT so in-flight requests finish before a worker exits.

Common mistakes

  • Running the app as root.
  • Exposing the Node.js port directly instead of through the proxy.
  • Storing sessions or uploaded files inside one worker's memory or on a path that is not backed up.
  • No memory limit, so a leak takes the whole VPS down. PM2's max_memory_restart option restarts a worker that grows past a limit.

Frequently asked questions

How many PM2 instances should I run?

One per vCPU is the usual start. Leave headroom if the same VPS also runs a database or Redis.

PM2 or systemd?

Either works. PM2 adds cluster mode, zero-downtime reloads and log handling. systemd alone is fine for a single process.

Do I need Nginx if Node.js can serve HTTPS?

Nginx handles TLS, compression, static files, rate limits and slow clients well, and keeps the app process simpler.


Sources