Install Node.js and PM2 on Ubuntu VPS

Node.js is a JavaScript runtime that lets you run server-side code — perfect for building APIs, web apps, real-time applications, and microservices. PM2 is a production-grade Process Manager that keeps your Node.js app running as a background daemon, automatically restarts it on crash, and launches it on every server reboot. This guide walks through the complete setup from a bare Ubuntu VPS to a running, secured application.

System Requirements

Step 1: Install Node.js via NVM

Using NVM (Node Version Manager) is strongly recommended over apt install nodejs. NVM lets you manage multiple Node.js versions, switch between them instantly, and install packages globally without sudo.

Install NVM

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Reload your shell so the nvm command becomes available:

source ~/.bashrc

Verify the installation:

nvm --version

Install the LTS Release of Node.js

nvm install --lts nvm use --lts nvm alias default node

Confirm the installed versions:

node -v npm -v

Why LTS? LTS (Long-Term Support) releases receive security patches and bug fixes for over 30 months, making them the right choice for production servers. Current releases are for trying out new features, not running live applications.

Step 2: Create a Test Node.js Application

Create a project folder and scaffold a minimal Express app:

mkdir -p ~/myapp && cd ~/myapp npm init -y npm install express

Create the entry file app.js:

cat > app.js << 'EOF' const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; app.get('/', (req, res) => { res.json({ message: 'Hello from AsiaGB VPS!', status: 'ok' }); }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); EOF

Test it directly before adding PM2:

node app.js

Open a second terminal and run: curl http://localhost:3000 — you should see the JSON response. Press Ctrl+C to stop.

Step 3: Install and Configure PM2

PM2 acts as a production Process Manager — it keeps your app running as a background service, restarts it on crash, and survives server reboots.

npm install -g pm2

Start Your App with PM2

cd ~/myapp pm2 start app.js --name "myapp"

Check the process status and live logs:

pm2 status pm2 logs myapp

Enable Auto-start on Reboot

pm2 startup # Copy the command PM2 prints and run it, e.g.: # sudo env PATH=$PATH:/home/user/.nvm/versions/node/v20.x.x/bin pm2 startup systemd -u user --hp /home/user pm2 save

Useful PM2 Commands: pm2 restart myapp — restart · pm2 stop myapp — stop · pm2 delete myapp — remove from PM2 · pm2 monit — real-time CPU/memory dashboard

Step 4: Set Up Nginx as a Reverse Proxy

Node.js runs on port 3000 internally, but visitors connect on port 80 (HTTP) or 443 (HTTPS). Nginx bridges the two — it accepts public traffic and forwards it to Node.js.

Install Nginx

apt update && apt install nginx -y ufw allow 'Nginx Full'

Create a Server Block

nano /etc/nginx/sites-available/myapp

Add the following config (replace yourdomain.com with your real domain):

server { listen 80; server_name yourdomain.com www.yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_cache_bypass $http_upgrade; } }

Enable the site and reload Nginx:

ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/ nginx -t systemctl reload nginx

Visit http://yourdomain.com — you should see the JSON from your Node.js app.

Step 5: Install SSL with Certbot (Recommended)

Add Let's Encrypt SSL so your app serves HTTPS traffic:

apt install certbot python3-certbot-nginx -y certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot automatically updates your Nginx config for HTTPS and sets up auto-renewal — no manual steps needed.

Install Node.js via NodeSource (Alternative to NVM)

If your VPS is multi-user, or you have several services that should all share one system-wide Node.js version, installing from the NodeSource repository is often simpler than NVM. The Node.js binary lands in the global path for every user and works with systemd out of the box, without any extra PATH configuration. This makes it a predictable choice for production servers that value simplicity.

Add the NodeSource repo for Node.js 20 LTS and install it:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs node -v npm -v

If you need to compile native modules (such as bcrypt or sharp) from source, install the build toolchain as well:

sudo apt install -y build-essential

NVM or NodeSource? Use NVM when you switch between versions frequently or develop on a single machine. Use NodeSource when you want one system-wide Node.js for production so systemd and PM2 can locate the binary easily. For a typical AsiaGB VPS running one app per server, NodeSource is usually the lower-maintenance option.

Run Your App in PM2 Cluster Mode

By default, Node.js runs on a single CPU core, leaving the rest of your VPS underutilized. PM2's cluster mode spreads incoming requests across multiple worker processes — one per core — using Node.js's built-in cluster module. You get higher throughput and zero-downtime reloads in one feature.

Start a cluster with the instance count set to max (equal to the number of CPU cores):

pm2 start app.js --name "myapp" -i max

Or pin a fixed number of workers, e.g. 2 processes (ideal for a 2-core VPS):

pm2 start app.js --name "myapp" -i 2

Rather than typing flags every time, create an ecosystem.config.js file to keep all your configuration in one place — environment variables, instance count, and a memory limit:

cat > ecosystem.config.js << 'EOF' module.exports = { apps: [{ name: 'myapp', script: './app.js', instances: 'max', exec_mode: 'cluster', max_memory_restart: '300M', env: { NODE_ENV: 'production', PORT: 3000 } }] }; EOF

Then launch everything with a single file:

pm2 start ecosystem.config.js pm2 save

The max_memory_restart option restarts a worker automatically when it exceeds the set RAM threshold — a simple guard against memory leaks crashing your app on a RAM-constrained VPS.

Cluster mode caveat: If your app stores state in memory (for example in-memory sessions), each worker has its own isolated copy and they will not share data. Move sessions to an external store such as Redis or a database instead.

Monitoring, Logs, and Zero-Downtime Reload

Once your app is in production, the priorities become watching process health and deploying new code without serving errors to users. PM2 ships with the tooling for both.

Watch Resources and Logs in Real Time

pm2 monit # live CPU/memory dashboard per process pm2 logs myapp # stream live logs (stdout + stderr) pm2 logs myapp --lines 200 # show the last 200 log lines pm2 show myapp # process details (uptime, restarts, path)

Rotate Logs So They Don't Fill the Disk

PM2 logs grow indefinitely and can eventually fill your VPS disk. Install the pm2-logrotate module to rotate and compress logs automatically:

pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 10M pm2 set pm2-logrotate:retain 7

The settings above cap each file at 10 MB and keep the last 7 files.

Zero-Downtime Reload After Code Updates

The restart command stops every process at once, causing a brief outage. In cluster mode, use reload instead — PM2 replaces workers one at a time while the others keep serving requests, so users never see an error:

git pull npm install --production pm2 reload myapp

With pm2 startup and pm2 save already configured, your app relaunches on every VPS reboot, helping your service meet the 99% uptime standard offered on AsiaGB VPS.

Tip: Expose a health-check endpoint such as /healthz in your app and have an external monitor (like Uptime Kuma) ping it every minute. You'll know the moment something breaks — before your users report it.

Frequently Asked Questions

How is PM2 different from systemd — which should I use?

systemd is Ubuntu's init system, managing OS-level services, while PM2 is a Node.js-specific process manager that bundles cluster mode, log management, and zero-downtime reload. In practice you use both together: pm2 startup generates a systemd unit that launches PM2 at boot, and PM2 then supervises your Node.js processes.

How much VPS RAM do I need to run Node.js + PM2 comfortably?

A small Express app runs fine on a 512 MB VPS, but if you enable cluster mode with several workers or handle high traffic, 1 GB or more is recommended. AsiaGB Linux VPS starts at ฿500/month on Ubuntu with full root access, and you can upgrade RAM/CPU as your app grows.

My app runs with node app.js but returns 502 Bad Gateway through Nginx — why?

This usually has two causes: PM2 is not actually running the app (check with pm2 status that it shows online), or the port in Nginx's proxy_pass doesn't match the port the app listens on. Confirm both are 3000, then run nginx -t and systemctl reload nginx.

I deployed new code but PM2 still runs the old version — what now?

PM2 does not reload files automatically after a git pull. Run pm2 reload myapp (cluster mode) or pm2 restart myapp to load the new code. If you changed values in ecosystem.config.js, use pm2 reload ecosystem.config.js --update-env so the new environment variables take effect.

Summary

Need a VPS for Node.js?

AsiaGB Linux VPS starts at ฿500/month · Full Root Access · SSD Storage · Unlimited Bandwidth · Choose any Ubuntu version

View VPS Plans

View all affordable VPS Thailand plans →