How to Install Node.js on VPS (2025) — LTS Setup Guide
Why Node.js on a VPS Is a Powerful Combination
Node.js has become one of the most popular runtimes for building scalable web applications, APIs, real-time services, and microservices. For deploying Python alongside Node.js, see Deploy FastAPI on VPS. Its non-blocking, event-driven architecture handles thousands of concurrent connections efficiently on a single thread, making it ideal for I/O-bound workloads like web servers, chat applications, and REST APIs. For web server setup, see Install Nginx on VPS.
Running Node.js on a VPS gives you full control over your runtime environment, process management, and deployment pipeline — without the overhead and cost of platform-as-a-service providers. For containerized setups, see Docker on Ubuntu VPS. A VPS with 2GB RAM can comfortably serve thousands of requests per second with proper optimization. For hosting recommendations, see VPS for Web Hosting and Setup n8n on VPS.
This guide walks you through a complete production setup: installing Node.js LTS from NodeSource, setting up PM2 for process management, deploying an Express.js application behind an Nginx reverse proxy with SSL, and configuring monitoring and auto-restart capabilities.
Prerequisites
| Requirement | Details |
|---|---|
| Operating System | Ubuntu 22.04 LTS or 24.04 LTS |
| Root Access | SSH access with root privileges or a sudo user |
| RAM | Minimum 512MB (1GB+ recommended for production Node.js apps) |
| Domain Name | Required for SSL certificate |
| Node.js Version | 22.x LTS (Long Term Support, supported until April 2027) |
Step 1: Install Node.js via NodeSource (LTS)
The Node.js version in Ubuntu's default repositories is often outdated. For the latest LTS release, use the NodeSource repository, which is maintained by the Node.js community and provides up-to-date packages for all active LTS versions.
Connect to your VPS and update the system.
ssh root@your-vps-ip
apt update && apt upgrade -y
Install the prerequisites and add the NodeSource repository for Node.js 22 LTS.
apt install -y ca-certificates curl gnupg
# Add NodeSource GPG key
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
# Add the NodeSource repository for Node.js 22.x LTS
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
apt update
Install Node.js. The nodejs package includes both Node.js and npm (the package manager).
apt install -y nodejs
Step 2: Verify Installation
Confirm that Node.js and npm are installed with the correct versions.
node --version
# Expected output: v22.x.x
npm --version
# Expected output: 10.x.x
# Check the installation path
which node
# Expected output: /usr/bin/node
which npm
# Expected output: /usr/bin/npm
Node.js 22 LTS includes several performance improvements over earlier versions: native test runner, WebSocket client, enhanced single executable applications, and improved ECMAScript module support. The LTS release receives security updates for 30 months after the initial release, making it the recommended choice for production.
Install Useful Global Packages
# PM2 process manager (covered in detail in Step 3)
npm install -g pm2
# Nodemon for development (auto-restart on file changes)
npm install -g nodemon
Note: Avoid installing too many global packages. Use npx to run packages without installing them globally. Use --save-dev for development-only dependencies in your project's package.json to keep production builds lean.
Step 3: Set Up PM2 Process Manager
PM2 is the de facto standard process manager for Node.js in production. It keeps your application running, automatically restarts it if it crashes, handles log management, enables cluster mode for multi-core utilization, and provides zero-downtime reloads.
Install PM2
npm install -g pm2
Key PM2 Features
- Process monitoring: Tracks CPU and memory usage per process
- Auto-restart: Restarts crashed applications automatically
- Cluster mode: Runs multiple instances across all CPU cores for maximum performance
- Log management: Captures stdout/stderr logs with built-in rotation
- Startup script: Generates init scripts to survive server reboots
- Zero-downtime reload: Restarts processes without dropping connections
- Remote monitoring: PM2 Plus (free tier) provides a web dashboard
Basic PM2 Commands
# Start an application
pm2 start app.js
# Start with a name
pm2 start app.js --name "my-api"
# Start in cluster mode (one instance per CPU core)
pm2 start app.js -i max
# List all running processes
pm2 list
# View logs
pm2 logs
# View logs for a specific app
pm2 logs my-api
# Monitor CPU and memory usage
pm2 monit
# Stop an application
pm2 stop my-api
# Restart an application
pm2 restart my-api
# Delete an application from PM2's process list
pm2 delete my-api
Step 4: Create a Sample Express.js Application
Create a project directory and build a simple Express.js API to verify the deployment pipeline works end-to-end.
mkdir -p /opt/nodeapp && cd /opt/nodeapp
Initialize the project and install Express.js.
npm init -y
npm install express
Create the application file.
cat > app.js << 'EOF'
const express = require('express');
const os = require('os');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
// Routes
app.get('/', (req, res) => {
res.json({
message: 'Node.js is running on VPS!',
version: process.version,
platform: os.platform(),
hostname: os.hostname(),
uptime: process.uptime()
});
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.get('/api/info', (req, res) => {
res.json({
cpuCount: os.cpus().length,
totalMemory: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
freeMemory: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
loadAverage: os.loadavg()
});
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
app.listen(PORT, '127.0.0.1', () => {
console.log(`Server running on http://127.0.0.1:${PORT}`);
});
EOF
Test the application by running it directly.
node app.js
Open another terminal and test the API.
curl http://127.0.0.1:3000/
curl http://127.0.0.1:3000/health
You should see JSON responses. Press Ctrl+C to stop the server, then proceed to run it through PM2.
Step 5: Configure PM2 Ecosystem File
For production deployments, use a PM2 ecosystem file. This declarative configuration file defines how your application should run, including the number of instances, memory limits, log configuration, and environment variables.
cat > ecosystem.config.js << 'EOF'
module.exports = {
apps: [{
name: 'nodeapp',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
port: 3000,
env: {
NODE_ENV: 'production',
PORT: 3000
},
env_development: {
NODE_ENV: 'development',
PORT: 3000
},
// Auto-restart configuration
autorestart: true,
watch: false,
max_memory_restart: '300M',
// Logging
error_file: '/var/log/nodeapp/error.log',
out_file: '/var/log/nodeapp/out.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss',
// Restart delay (ms)
restart_delay: 4000,
// Time to wait before killing the process on reload
kill_timeout: 5000,
// Listen timeout
listen_timeout: 10000
}]
};
EOF
Create the log directory.
mkdir -p /var/log/nodeapp
Ecosystem File Settings Explained
- instances: 'max': Creates one process per CPU core. PM2's cluster mode distributes incoming connections across all instances, maximizing CPU utilization. On a 4-core VPS, this creates 4 Node.js processes
- exec_mode: 'cluster': Enables PM2's cluster mode, which uses Node.js's built-in cluster module to load-balance across multiple processes
- max_memory_restart: '300M': Automatically restarts the process if it consumes more than 300MB of RAM. This prevents memory leaks from accumulating over time
- restart_delay: 4000: Waits 4 seconds between restart attempts to prevent rapid restart loops when the application fails on startup
- env / env_development: Define different environment variables per environment. Start with
pm2 start ecosystem.config.js --env production
Start the Application
cd /opt/nodeapp
pm2 start ecosystem.config.js --env production
Verify the application is running.
pm2 list
curl http://127.0.0.1:3000/
Step 6: Set Up Nginx Reverse Proxy
Nginx handles SSL termination, static file serving, request buffering, and security headers while forwarding API requests to the Node.js application. This is the standard production architecture for Node.js deployments.
Install Nginx
apt install nginx -y
systemctl enable nginx
systemctl start nginx
Create the Server Block
cat > /etc/nginx/sites-available/nodeapp << 'EOF'
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
# Redirect HTTP to HTTPS (after SSL is configured)
# return 301 https://$host$request_uri;
# Security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 4;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
# Logging
access_log /var/log/nginx/nodeapp.access.log;
error_log /var/log/nginx/nodeapp.error.log;
# Reverse proxy to Node.js application
location / {
proxy_pass http://127.0.0.1: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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Buffering settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Static files (serve directly from Nginx)
location /static/ {
alias /opt/nodeapp/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Block access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
EOF
Enable the server block and test the configuration.
ln -s /etc/nginx/sites-available/nodeapp /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
Allow HTTP through the firewall.
ufw allow 'Nginx Full'
ufw reload
Visit http://your-domain.com in your browser. You should see the JSON response from your Node.js application.
Step 7: Install SSL with Certbot
Secure your application with a free Let's Encrypt SSL certificate. Certbot integrates with Nginx to automatically configure SSL.
apt install certbot python3-certbot-nginx -y
Obtain the SSL certificate. Certbot will modify your Nginx configuration to enable HTTPS and redirect HTTP to HTTPS.
certbot --nginx -d your-domain.com -d www.your-domain.com
Follow the prompts:
- Enter your email address for renewal notifications
- Agree to the terms of service
- Choose option 2 to redirect HTTP to HTTPS
Verify automatic renewal.
systemctl status certbot.timer
certbot renew --dry-run
Test that HTTPS works.
curl -I https://your-domain.com
You should see a 200 OK response with HTTP/2 and the Strict-Transport-Security header.
Step 8: Configure Environment Variables
Environment variables store configuration that varies between environments (database URLs, API keys, feature flags). Never hardcode secrets in your source code.
Create a .env File
cat > /opt/nodeapp/.env << 'EOF'
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your_random_secret_key_here
API_RATE_LIMIT=100
EOF
Load .env in Your Application
Install the dotenv package to load environment variables from the .env file.
cd /opt/nodeapp
npm install dotenv
Add dotenv to the top of your application file (before requiring any modules that depend on environment variables).
cat > app.js << 'EOF'
// Load environment variables from .env file
require('dotenv').config();
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({
message: 'Node.js is running on VPS!',
environment: process.env.NODE_ENV
});
});
app.listen(PORT, '127.0.0.1', () => {
console.log(`Server running on port ${PORT} (${process.env.NODE_ENV})`);
});
EOF
Pass Environment Variables via PM2
You can also pass environment variables directly through the PM2 ecosystem file (as shown in Step 5) or at startup time.
# Start with environment variable override
NODE_ENV=production pm2 restart nodeapp
# Or set variables permanently in the ecosystem file
pm2 set nodeapp:NODE_ENV production
Security Warning: Never commit your .env file to version control. Add .env to your .gitignore file. Use .env.example as a template that contains variable names without actual values.
Step 9: Auto-Restart on Reboot
PM2 provides a startup script that registers your applications as system services. This ensures that your Node.js application starts automatically when the VPS reboots.
Generate and Install the Startup Script
# Generate the startup script
pm2 startup
# The command output will show you a specific command to run.
# Copy and execute that command (it will look like one of these):
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root
# sudo env PATH=$PATH:/usr/bin pm2 startup ubuntu -u root --hp /root
Save the current process list so PM2 knows which applications to restart on boot.
pm2 save
Verify the startup configuration.
pm2 list
systemctl status pm2-root
The output should show your applications listed and the PM2 service as active. After a reboot, PM2 will automatically restore and start all saved applications.
Test the Auto-Restart
reboot
After the VPS reboots (1-2 minutes), reconnect and verify that your application is running.
ssh root@your-vps-ip
pm2 list
curl http://127.0.0.1:3000/health
Step 10: Monitor with PM2 Plus
PM2 Plus is a free monitoring dashboard that provides real-time metrics for your PM2-managed applications, including CPU usage, memory consumption, event logs, and custom metrics.
Set Up PM2 Plus
# Link your PM2 instance to PM2 Plus (requires a free account at pm2.io)
pm2 link YOUR_SECRET_KEY YOUR_PUBLIC_KEY
Alternatively, use PM2's built-in monitoring directly on the VPS.
# Real-time terminal monitoring
pm2 monit
Log Management
PM2 captures stdout and stderr logs for each application. Set up log rotation to prevent logs from consuming all disk space.
# Install PM2 log rotation module
pm2 install pm2-logrotate
# Configure log rotation
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
Useful PM2 Monitoring Commands
# View real-time metrics
pm2 monit
# View detailed information about a specific app
pm2 show nodeapp
# View logs with timestamps
pm2 logs --lines 100
# Check CPU and memory usage
pm2 list
# Dump current process list for backup
pm2 prettylist > pm2-backup.json
Custom Monitoring with Express
Add a metrics endpoint to your Express application for integration with Prometheus or other monitoring tools.
npm install prom-client
// Add to app.js
const promClient = require('prom-client');
const collectDefaultMetrics = promClient.collectDefaultMetrics;
collectDefaultMetrics({ register: promClient.register });
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
EOF
Troubleshooting Common Node.js Issues on VPS
1. EADDRINUSE — Port Already in Use
Cause: Another process is already using port 3000, or a previous Node.js instance did not shut down cleanly.
Solution: Find and kill the process using the port: ss -tlnp | grep 3000 followed by kill PID. Or use PM2 to stop the application: pm2 stop nodeapp.
2. Application Crashes on Startup
Cause: Missing dependencies, incorrect environment variables, or syntax errors.
Solution: Check PM2 logs: pm2 logs nodeapp --lines 50. Run the application directly to see stack traces: node app.js. Verify that npm install completed successfully and all dependencies are present in node_modules.
3. High Memory Usage
Cause: Memory leak in the application, or the application legitimately needs more memory.
Solution: Set max_memory_restart in the PM2 ecosystem file to auto-restart when memory exceeds a threshold. Use the Node.js built-in heapdump module to capture heap snapshots for analysis. Profile the application with --inspect and Chrome DevTools.
4. 502 Bad Gateway from Nginx
Cause: Nginx cannot connect to the Node.js application. The app may have crashed or is listening on a different port/address.
Solution: Verify the app is running with pm2 list and curl http://127.0.0.1:3000. Check that the proxy_pass address in the Nginx config matches the app's actual listen address. Review the Nginx error log at /var/log/nginx/nodeapp.error.log.
5. npm install Fails with Permission Errors
Cause: Running npm as root can cause permission issues, especially with global packages.
Solution: Create a dedicated user for Node.js applications: useradd -m -s /bin/bash nodeuser. Run applications as this user and use sudo only for PM2 startup configuration. Alternatively, change npm's default directory: mkdir ~/.npm-global && npm config set prefix '~/.npm-global'.
Frequently Asked Questions
Should I use Node.js LTS or Current?
Always use the LTS (Long Term Support) version for production. LTS releases receive security updates and bug fixes for 30 months. The Current release line includes the latest features but has a shorter support window and may contain breaking changes. Node.js 22 LTS is supported until April 2027.
How much RAM does a Node.js application need?
A basic Express.js API uses 30-50MB of RAM per process. With PM2 cluster mode on a 4-core VPS (4 processes), total Node.js memory usage is approximately 120-200MB. Add memory for Nginx, PostgreSQL, and the operating system. A 2GB VPS is sufficient for most small to medium applications.
What is the difference between PM2 cluster mode and a single process?
Cluster mode runs multiple Node.js instances (one per CPU core) behind a built-in load balancer. This maximizes CPU utilization because Node.js runs on a single thread by default. A single-process deployment only uses one core, leaving other cores idle. Cluster mode provides near-linear performance scaling across cores.
Can I run multiple Node.js applications on one VPS?
Yes. Each application should listen on a different port (3000, 3001, 3002, etc.) and have its own Nginx server block that proxies to the correct port. PM2 manages all applications independently. A 2GB VPS can comfortably run 3-5 small Node.js applications alongside Nginx.
How do I deploy updates with zero downtime?
Use PM2's reload command: pm2 reload nodeapp. In cluster mode, PM2 restarts one worker at a time, ensuring that at least one worker is always handling requests. For code changes, pull the latest code, run npm install --production, then reload.
Should I use PM2 or Docker for Node.js?
PM2 is simpler and has less overhead, making it ideal for running Node.js directly on a VPS. Docker provides better isolation and reproducibility, which is important for complex multi-service deployments. For a single Node.js application, PM2 is often the better choice due to its lightweight nature and built-in cluster mode.
How do I handle static files in a Node.js application?
Serve static files through Nginx instead of Node.js. Nginx is significantly more efficient at serving static content. Add a location /static/ block in your Nginx config that points to the directory containing your static files. Use expires 30d and Cache-Control: public, immutable headers for cacheable assets.
How do I enable CORS in my Express.js API?
Install the cors package: npm install cors. Add it as middleware: const cors = require('cors'); app.use(cors({ origin: 'https://your-frontend.com' }));. For production, always restrict the allowed origins instead of using app.use(cors()) which allows all origins.