This is the exact setup I used to deploy MasterPrompting.net on a Hostinger KVM 2 VPS. The stack: Ubuntu 22.04, Node.js 20, PM2, Nginx, Let's Encrypt SSL, with GitHub Actions for automatic deployments.
By the end of this guide you'll have a production-ready deployment that:
- Serves your Next.js app on your custom domain over HTTPS
- Automatically restarts on crash (PM2)
- Deploys on every
git pushto main (GitHub Actions) - Handles HTTP → HTTPS redirect automatically
If you haven't picked a VPS yet, I'm on the Hostinger KVM 2 plan — it's what this guide is written for.
Prerequisites
- A Hostinger KVM VPS with Ubuntu 22.04 (select at setup time)
- A domain name pointed at your VPS IP (A record)
- Your Next.js app in a GitHub repository
- Basic comfort with the terminal
Step 1: Initial Server Setup
After purchasing, you'll get an IP address and root credentials (or you can add an SSH key in hPanel). SSH in:
ssh root@YOUR_VPS_IP
Create a non-root user
Running everything as root is a security risk. Create a deploy user:
adduser deploy
usermod -aG sudo deploy
Copy your SSH key to the deploy user
# Still as root:
mkdir /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
Now log in as deploy:
ssh deploy@YOUR_VPS_IP
Basic firewall setup
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status
Step 2: Install Node.js
Use the Node Version Manager (NVM) — makes upgrading Node easy later:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
nvm alias default 20
node --version # should print v20.x.x
npm --version
Step 3: Install PM2
PM2 keeps your Next.js server running and restarts it automatically if it crashes:
npm install -g pm2
pm2 --version
Set PM2 to start automatically on server reboot:
pm2 startup
# Run the command it prints (something like: sudo env PATH=... pm2 startup...)
Step 4: Clone and Build Your Next.js App
cd /home/deploy
git clone https://github.com/YOUR_USERNAME/YOUR_REPO.git app
cd app
npm install
npm run build
Make sure the build succeeds locally first — a failed build on the server gives less helpful errors.
Configure environment variables
Create a .env.production file (or .env.local — check what your app expects):
nano .env.production
Add your environment variables:
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
# add any other vars your app needs
Step 5: Start with PM2
pm2 start npm --name "my-nextjs-app" -- start
pm2 save # saves the process list so it restarts after reboot
pm2 status
Check it's running:
pm2 logs my-nextjs-app --lines 20
Your app is now running on port 3000 (Next.js default). We'll route traffic to it via Nginx in the next step.
To use a different port, add PORT=3001 to your .env.production — useful when running multiple apps.
Step 6: Install and Configure Nginx
Nginx sits in front of your Next.js app and handles:
- Routing requests from port 80/443 to port 3000
- SSL termination
- HTTP → HTTPS redirect
- Serving static assets directly (optional optimisation)
sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx
Create a server block for your domain
sudo nano /etc/nginx/sites-available/yourdomain.com
Paste this configuration (replace yourdomain.com and the port if needed):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Proxy all requests to Next.js on port 3000
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_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;
}
# Serve Next.js static assets directly for better performance
location /_next/static/ {
alias /home/deploy/app/.next/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Enable the site and test:
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t # test config — must say "ok"
sudo systemctl reload nginx
Visit http://yourdomain.com in your browser — you should see your Next.js app.
Step 7: SSL with Let's Encrypt
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Follow the prompts (enter your email, agree to terms). When it asks about HTTP redirect, choose option 2 (redirect all HTTP to HTTPS).
Certbot automatically edits your Nginx config to add SSL. Verify it works by visiting https://yourdomain.com.
Auto-renewal
Certbot installs a systemd timer that renews certificates automatically. Test it:
sudo certbot renew --dry-run
If the dry run passes, you're set. Certificates renew automatically 30 days before expiry.
Step 8: Automatic Deploys from GitHub
Manually SSH-ing to pull and rebuild every time you push code gets old fast. Here's a GitHub Actions workflow that does it automatically.
Add your VPS SSH key as a GitHub Secret
On your server, generate a deploy key:
ssh-keygen -t ed25519 -C "github-deploy" -f ~/.ssh/github_deploy -N ""
cat ~/.ssh/github_deploy.pub >> ~/.ssh/authorized_keys
cat ~/.ssh/github_deploy # copy this — it's your private key
In your GitHub repo: Settings → Secrets and variables → Actions → New repository secret
Add these secrets:
VPS_HOST— your server IPVPS_USER—deployVPS_SSH_KEY— paste the private key (cat ~/.ssh/github_deploy)
Create the workflow file
In your repo, create .github/workflows/deploy.yml:
name: Deploy to VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to Hostinger VPS
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /home/deploy/app
git pull origin main
npm ci --production=false
npm run build
pm2 restart my-nextjs-app
pm2 save
Now every push to main automatically:
- SSHs into your VPS
- Pulls the latest code
- Installs dependencies
- Builds the app
- Restarts PM2 with zero-downtime
Check the Actions tab in GitHub to see deploys running.
Step 9: Set Up Cloudflare (Optional but Recommended)
For free global CDN, DDoS protection, and caching, put Cloudflare in front:
- Add your domain to Cloudflare (free plan)
- Update nameservers at your domain registrar to Cloudflare's
- Add an A record pointing to your VPS IP
- Set SSL/TLS to Full (Strict) in Cloudflare dashboard
- Enable Always Use HTTPS
With Cloudflare, your static assets (images, JS, CSS) are served from Cloudflare's edge — reducing load on your VPS and improving load times globally.
Monitoring and Logs
Useful PM2 commands:
pm2 status # show all running processes
pm2 logs my-nextjs-app # tail live logs
pm2 logs my-nextjs-app --lines 100 # last 100 log lines
pm2 monit # CPU/memory dashboard
pm2 restart my-nextjs-app # restart app
pm2 reload my-nextjs-app # zero-downtime reload (for clusters)
Nginx access and error logs:
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
Running Multiple Apps on One VPS
The KVM 2 plan has plenty of headroom to run multiple Next.js apps. For each additional app:
- Clone to a different directory (
/home/deploy/app2) - Start PM2 with a different name and port:
pm2 start npm --name "app2" -- start(setPORT=3001in.env) - Create a separate Nginx server block pointing to the new port
- Run Certbot for the new domain
This is one of the real advantages of a VPS over shared hosting — you can run as many apps as your RAM allows.
Troubleshooting
App not loading after deploy:
pm2 logs my-nextjs-app --lines 50 # check for build errors
Nginx 502 Bad Gateway:
The Next.js app isn't running. Check pm2 status — if it shows errored, check the logs.
SSL certificate not renewing:
sudo certbot renew --dry-run # test renewal
sudo systemctl status certbot.timer # check timer is active
High memory usage:
free -h # total/used/available RAM
pm2 monit # per-process breakdown
What This Setup Costs
| Component | Cost |
|---|---|
| Hostinger KVM 2 VPS (24-mo) | ~₹600–900/month |
| Domain name | ~₹800–1200/year |
| Cloudflare | Free |
| Let's Encrypt SSL | Free |
For a production Next.js app that would cost 5–10x more on AWS or GCP, this is a solid setup.
This is exactly the stack MasterPrompting.net runs on. If it's handling a prompt engineering education site, it'll handle whatever you're building.
If you haven't picked your VPS yet: Hostinger KVM 2 — India pricing (affiliate link)