How to Host Node.js on a VPS Using Nginx and PM2
Deploying a Node.js application to a Virtual Private Server (VPS) can feel intimidating if you are used to one-click platform-as-a-service (PaaS) providers. However, hosting on your own VPS gives you full control over your server environment, significantly lower costs, and better performance tuning.
To run a production-ready Node.js app, you need three core components:
Node.js: The runtime environment executing your code.
PM2: A production process manager that keeps your application alive forever, reloads it without downtime, and helps manage logging.
Nginx: A high-performance web server acting as a reverse proxy, handling SSL termination, and routing external traffic to your isolated Node.js application.
Here is a complete, step-by-step guide to setting up your environment on a clean Linux VPS (Ubuntu 24.04/22.04 LTS).
Step 1: Connect to Your VPS and Update the System
First, access your VPS via SSH. Open your terminal and run:
ssh root@your_server_ip
Once logged in, ensure your package index is updated and existing software is upgraded to the latest secure versions:
sudo apt update && sudo apt upgrade -y
Step 2: Install Node.js (via NVM)
While Ubuntu has Node.js in its default repositories, it is often an outdated version. Using Node Version Manager (NVM) allows you to install and manage specific Node.js versions easily.
- Download and run the NVM installation script:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
- Activate NVM in your current terminal session:
source ~/.bashrc
- Install the Long-Term Support (LTS) version of Node.js:
nvm install --lts
- Verify the installation:
node -v
npm -v
Step 3: Prepare Your Node.js Project
For this guide, we will set up a simple production directory and create a boilerplate Express application. If you have an existing project, you can git clone it into /var/www/myapp.
- Create a home directory for your application:
mkdir -p /var/www/myapp
cd /var/www/myapp
- Initialize a new Node.js project:
npm init -y
- Install Express:
npm install express
- Create an
index.jsfile:
nano index.js
- Paste the following production boilerplate code into the file:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('<h1>Node.js app is successfully running on this VPS!</h1>');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
(Press CTRL + O, Enter to save, and CTRL + X to exit Nano).
Step 4: Configure PM2 to Manage Your App
If you run your app using node index.js, the process will terminate the moment you close your SSH terminal. PM2 solves this by running your app as a background service.
- Install PM2 globally:
npm install pm2 -g
- Start your application with PM2:
pm2 start index.js --name "myapp"
- Ensure PM2 automatically restarts your application if the VPS reboots:
pm2 startup
This command will output a specific environment script string. Copy and paste that line into your terminal and press Enter.
4. Save the current process list configuration:
pm2 save
Useful PM2 Commands:
pm2 status– Monitor running processes.pm2 logs myapp– View live application logs.pm2 restart myapp– Restart the application after code changes.
Step 5: Install and Configure Nginx as a Reverse Proxy
Currently, your Node.js app runs locally on port 3000. We need Nginx to accept public web requests on port 80 (HTTP) and route them to port 3000 internally.
- Install Nginx:
sudo apt install nginx -y
- Remove the default Nginx placeholder configuration:
sudo rm /etc/nginx/sites-enabled/default
- Create a new configuration file for your app:
sudo nano /etc/nginx/sites-available/myapp
- Add the following reverse proxy configuration block. Replace
yourdomain.comwith your actual domain name or server IP address:
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_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
- Enable the configuration by creating a symbolic link to the
sites-enableddirectory:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
- Test your configuration for syntax errors:
sudo nginx -t
- If the test passes, restart Nginx:
sudo systemctl restart nginx
Step 6: Configure UFW Firewall and SSL (Recommended)
To protect your server, ensure your firewall only allows web traffic through specific open ports.
- Allow SSH, HTTP, and HTTPS traffic through the Uncomplicated Firewall (UFW):
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
To secure your application with free HTTPS using Let's Encrypt and Certbot:
- Install Certbot and its Nginx plugin:
sudo apt install certbot python3-certbot-nginx -y
- Run Certbot to generate and install your SSL certificate automatically:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
- Follow the on-screen prompts. Certbot will automatically rewrite your Nginx configuration to force secure SSL (
https://) connections.
Conclusion
Your Node.js application is now officially fully production-ready, highly secure, and live. Nginx handles your incoming public web traffic smoothly, PM2 guarantees that your application processes will heal and restart themselves seamlessly if an unexpected crash or server reboot occurs, and SSL keeps user data safe.
When you push updates to your source code, all you need to do is pull down the new changes to your app directory and execute pm2 reload myapp for zero-downtime deployments!
