
Deploying Node.js Apps on a Linux Server with Nginx, PM2, and Certbot within minutes
A step-by-step guide to deploying a Node.js application on a Linux server, including server setup, Nginx reverse proxy configuration, SSL with Certbot, and process management using PM2.
One of the most important parts of building projects is being able to showcase them and make them accessible for the world to use. Deployment on virtual machines can be a challenge for beginners and sometimes even experienced developers.
In this article, we will go through a step-by-step guide on how to deploy projects on a virtual machine. We will focus on Node.js-based projects, but apart from a few steps, most of the process remains same for deploying projects regardless of stack.
Prerequisites
You will need a Linux server (Ubuntu), you can use any cloud provider like DigitalOcean, AWS, Hetzner, etc. You will also need a domain name pointed to your server's IP address and SSH access to your server.
Step 1: Log into Your Server
SSH into your server using your private key:
ssh -i ./your-key root@your-server-ipReplace your-key with the path to your SSH key and your-server-ip with your server's actual IP address.
Once you're in, update the system packages:
sudo apt update && sudo apt upgrade -yStep 2: Install Required Packages
Install Nginx
Nginx will act as a reverse proxy, it sits in front of your Node.js app and forwards requests to it.
sudo apt install nginxInstall Certbot (for SSL)
Certbot automates the process of obtaining and renewing SSL certificates from Let's Encrypt. We install it via snap to get the latest version.
sudo apt install snapd
sudo snap install --classic certbotYou can also refer to the official Certbot instructions for Nginx at certbot.eff.org.
Install Unzip
You might need unzip for extracting archives on the server:
sudo apt install unzipInstall Bun (or Node.js)
If you're using Bun as your runtime:
curl -fsSL https://bun.sh/install | bashIf you prefer Node.js, you can install it via nvm or directly from the NodeSource repository.
Install PM2
PM2 is a process manager that keeps your app running in the background, restarts it on crashes, and manages logs for you.
npm install -g pm2Step 3: Clone and Build Your Project
Clone your project repository on the server:
git clone https://github.com/your-username/your-repo.git
cd your-repoInstall dependencies:
npm installNow build the project. If your server has limited memory (which is common on smaller VMs), you can increase the Node.js heap size to prevent out-of-memory crashes:
NODE_OPTIONS="--max-old-space-size=1024" npm run buildThis is especially useful on servers with 1GB RAM or less.
Step 4: Configure Nginx as a Reverse Proxy
Nginx will handle all the incoming HTTP/HTTPS traffic and forward it to your Node.js process running on a local port.
Check Nginx Status
First, make sure Nginx is running:
sudo systemctl status nginxEdit the Nginx Configuration
Open the Nginx config file:
sudo nano /etc/nginx/nginx.confOr if you prefer Vim:
vim /etc/nginx/nginx.confAdd a Server Block
Inside the http block of your nginx.conf (or as a separate file in /etc/nginx/sites-available/), add a server block for your project:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
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;
}
}Replace yourdomain.com with your actual domain and 3000 with the port your app runs on.
If you're deploying multiple projects on the same server, make sure each project uses a different port and has its own server block with a unique server_name. Ports should not overlap between projects.
Test and Restart Nginx
Always test the configuration before restarting:
sudo nginx -tIf the test passes, restart Nginx:
sudo systemctl restart nginxStep 5: Set Up SSL with Certbot
Now that Nginx is routing traffic to your app, we can secure it with HTTPS.
Get and install certificates (Recommended)
This is the easiest way. Certbot will detect your server blocks, ask which domains you want to secure, and handle the entire SSL configuration for you. It also sets up automatic renewal.
sudo certbot --nginxOr, just get a certificate
If you want to handle the Nginx SSL configuration yourself:
sudo certbot certonly --nginxAfter obtaining the certificate, restart Nginx:
sudo systemctl restart nginxStep 6: Start Your App with PM2
If you run your app with npm run start directly, it stops when you close the terminal. PM2 solves this by keeping it running as a background process.
Start the app:
pm2 start npm --name "my-app" -- run startReplace "my-app" with a name that helps you identify the project, and start with whatever script you need (dev, start, etc.).
If you're using Bun:
pm2 start bun --name "my-app" -- run startUseful PM2 Commands
# List all running processes
pm2 list
# View logs
pm2 logs my-app
# Restart an app
pm2 restart my-app
# Stop an app
pm2 stop my-app
# Delete an app from PM2
pm2 delete my-app
# Save the current process list (survives server reboot)
pm2 save
# Set PM2 to start on boot
pm2 startupAfter starting your app and confirming it works, run pm2 save and pm2 startup so your app automatically restarts if the server reboots.
Quick Reference
Here's the entire flow condensed for when you're deploying your next project:
# SSH into server
ssh -i ./your-key root@your-server-ip
# Clone and build
git clone https://github.com/you/your-repo.git
cd your-repo
npm install
NODE_OPTIONS="--max-old-space-size=1024" npm run build
# Add Nginx server block for your domain
sudo nano /etc/nginx/nginx.conf
# Test and restart Nginx
sudo nginx -t
sudo systemctl restart nginx
# SSL
sudo certbot --nginx
# Start with PM2
pm2 start npm --name "my-app" -- run start
pm2 saveThat's it. Your app is now live, secured with SSL, and managed by PM2. The whole process takes a few minutes once you've done it a couple of times.
