Introduction
Deploying a Next.js app to a VPS can feel tricky if it’s your first time. In this post, we’ll go through the basic steps from setting up your VPS to getting your app live using Nginx and PM2.
By the end of this guide, you will:
- Have a Next.js app running on your domain
- Understand the basics of how Nginx works as a reverse proxy
- Keep your app running smoothly with PM2
This is a beginner-friendly guide, so no advanced server skills needed.
Setup Checklist
Before we start, make sure you have:
- A VPS (Ubuntu or Another Linux)
- A domain name
- Node.js installed or ready to install
- Basic terminal/SSH access
- Your Next.js project ready to deploy
That’s it. Once you have these, we’re good to go.
Step 1: Connect to Your VPS
First, log in to your VPS using SSH.
Open your terminal and run:
ssh your-username@your-vps-ip
Step 2: Install Node.js and PM2
Once you’re in your VPS, install Node.js (LTS version) and PM2. PM2 is a process manager for Node.js apps. It keeps your app running 24/7, restarts it if it crashes, and can even start it automatically after a server reboot.
Run these commands:
# Update packages
sudo apt update && sudo apt upgrade -y
# Install Node.js LTS
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Install PM2 globally
sudo npm install -g pm2
To check if everything worked:
node -v
npm -v
pm2 -v
If you see version numbers, you’re good to go. For example:
v20.11.1
10.5.0
5.3.0
Step 3: Upload Your Next.js App to the VPS
Now we need to get your Next.js project onto the VPS.
The easiest way is to use Git (make sure your code is already pushed to GitHub, GitLab, or similar).
On your VPS, run:
# Install Git if you don't have it
sudo apt install -y git
# Clone your repository
git clone https://github.com/your-username/your-repo.git
# Go into the project folder
cd your-repo
Step 4: Build and Start the App with PM2
Once your project is on the VPS, install dependencies and build the app:
# Install dependencies
npm install
# Build the Next.js app
npm run build
Now start the app with PM2. Here’s a general template:
pm2 start "npm run start -- -p YOUR_PORT" \
--name "[YOUR_APP_NAME]" \
--cwd /path/to/your/project
Or if you already at the path of your folder project use this:
# Default port from your project (usually 3000)
pm2 start "npm run start" --name "[your_app_name]"
# Custom port
pm2 start "npm run start -- -p [your_custom_port]" --name "[your_app_name]"
Now check if it’s already running:
pm2 list
Example if it's already running
┌─────┬─────────────────────┬──────┬──────┬─────────┬───────────┬────────┐
│ id │ name │ mode │ pid │ status │ restart │ cpu │
├─────┼─────────────────────┼──────┼──────┼─────────┼───────────┼────────┤
│ 0 │ your_app_name │ fork │ 1234 │ online │ 0 │ 0% │
└─────┴─────────────────────┴──────┴──────┴─────────┴───────────┴────────┘
If the status says online, it means PM2 has started the process. However, “online” doesn’t always mean your app is working correctly. So, you should also check the logs.
pm2 logs [your_app_name]
Example output if it's already works:
0|[your_app_name] | ready - - Local: http://localhost:[your_port]
0|[your_app_name] | ready - ...
0|[your_app_name] | ready - ...
0|[your_app_name] | info ✓ Starting...
0|[your_app_name] | info ✓ Ready in 1163ms
If you see Ready
and no errors in the logs, your Next.js app is running successfully.
Step 5: Install and Configure Nginx
We’ll use Nginx as a reverse proxy.
Simply put, Nginx will take requests from your domain (port 80 or 443) and forward them to your Next.js app running on another port (like 3000 or 3002).
This way, visitors don’t have to type the port in the URL.
First, install Nginx:
sudo apt install -y nginx
Check if Nginx is running:
sudo systemctl status nginx
If it’s active (green), we’re good.
Next, create a config file for your app:
sudo nano /etc/nginx/sites-available/[your_app_name].conf
Example config:
server {
listen 80;
server_name [yourdomain] www.[yourdomain];
# for example:
# server_name instagram.com www.instagram.com;
location / {
proxy_pass http://localhost:[your_app_port];
# for example:
# 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;
}
}
Enable the config and restart Nginx:
sudo ln -s /etc/nginx/sites-available/[your_app_name].conf /etc/nginx/sites-enabled/[your_app_name].conf
Make sure the file name in sites-available
and sites-enabled
is exactly the same.
Check syntax:
sudo nginx -t
If nginx -t
shows syntax is ok and test is successful, it means the configuration is valid and ready to use.
sudo systemctl restart nginx
Now, if your domain is already pointed to your VPS IP in your DNS settings,
you can just type https://yourdomain.com
in the browser and it will load your Next.js app.
Step 6: Keep the App Running After Logout or Reboot
PM2 can keep your app alive even after you close SSH or the server reboots.
- Save the current process list
pm2 save
This tells PM2 to “Remember what I’m running right now.”
PM2 will print a command. Copy and run it. Example:
- Enable auto start on reboot
pm2 startup
PM2 will show a command, so just copy and run it.
Example:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u your_user --hp /home/your_user
- Enable auto start on reboot
pm2 save
Now, even if your VPS restarts, PM2 will automatically start your app again.
Step 7: Enable HTTPS with Certbot
Right now your app is running on HTTP (not secure).
Let’s make it secure with HTTPS using a free SSL certificate from Let’s Encrypt.
- Install Certbot and the Nginx plugin
sudo apt install -y certbot python3-certbot-nginx
- Request and install the SSL certificate
Replace yourdomain.com with your actual domain:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
- Follow the prompts
- Certbot will ask for your email (for renewal notices)
- Agree to the terms
- Choose whether to redirect HTTP to HTTPS (recommended: Yes)
- Check HTTPS
Open https://yourdomain.com
in your browser.
If the padlock icon appears, you’re good to go.
- Auto-renew
Let’s Encrypt SSL certificates only last 90 days.
If you don’t renew them, your site will lose HTTPS and show a “Not Secure” warning.
To make it renew automatically before it expires, enable Certbot’s auto-renew service:
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
Now your Next.js app is secure and will automatically keep its SSL certificate up to date.