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=devStep 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 systemdCluster 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 enableBind the Node.js process to 127.0.0.1 so only Nginx can reach it.
Step 6: logs and monitoring
pm2 logsfor 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
- Kill one worker with
pm2 stop 0and confirm requests still succeed. - Reboot the VPS and confirm the service comes back on its own.
- 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 plan | vCPU | DDR5 | NVMe | Monthly |
|---|---|---|---|---|
| 9950X VPS 16 | 4 | 16 GB | 64 GB | ₹2,399 |
| 9950X VPS 32 | 6 | 32 GB | 128 GB | ₹4,799 |
| 9950X VPS 48 | 8 | 48 GB | 192 GB | ₹7,199 |
| 9950X VPS 64 | 10 | 64 GB | 256 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_restartoption 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.