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

RequirementDetails
Operating SystemUbuntu 22.04 LTS or 24.04 LTS
Root AccessSSH access with root privileges or a sudo user
RAMMinimum 512MB (1GB+ recommended for production Node.js apps)
Domain NameRequired for SSL certificate
Node.js Version22.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

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

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:

  1. Enter your email address for renewal notifications
  2. Agree to the terms of service
  3. 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.

Need a Fast VPS for Node.js?

Inferno VPS with NVMe SSD and 10Gbps uplink — run your Node.js applications with blazing fast response times across European data centers.

Get Your VPS →