Thapa Technical — Dev Blog
Next.js PostgreSQL VPS Caddy PM2

Host a Full-Stack Next.js App with PostgreSQL on a VPS — Start to Finish.

Shared hosting can't run a real Next.js server. A VPS can. In this guide you'll take a full-stack Next.js + PostgreSQL app from a GitHub repo to a live, HTTPS-secured domain — pointing DNS, installing the database, running Node.js with PM2, and putting Caddy in front as a reverse proxy. No prior server experience needed.

What You'll Need

Before you start, make sure you have these ready. The whole deploy takes about 20–30 minutes once they're in place.

  • A VPS running Ubuntu (any provider — Hostinger, DigitalOcean, Linode, Hetzner, etc.) and its public IP
  • A domain name you control
  • Your full-stack Next.js app pushed to a GitHub repository
  • Basic comfort with the terminal — you'll copy/paste commands
Replace yourdomain.com, the database name, username and password with your own values everywhere they appear below.
1
DNS

Point Your Domain at the VPS

In your domain registrar's DNS settings, add two A records that point to your VPS's public IP address:

TypeName / HostValueTTL
A@Your VPS IP60
AwwwYour VPS IP60

DNS can take a few minutes to propagate. Confirm it's pointing to the right server:

bash — local machine
nslookup yourdomain.com

Once it resolves to your IP, SSH into the server:

bash — local machine
ssh root@yourdomain.com
Why a low TTL

A TTL of 60 seconds means DNS changes propagate fast while you're setting things up. You can raise it later once everything is stable.

2
Database

Install PostgreSQL & Create the Database

Update the package list and install PostgreSQL:

bash — on your VPS
sudo apt update
sudo apt install -y postgresql postgresql-contrib

Switch to the postgres system user and open the PostgreSQL shell:

bash — on your VPS
sudo -i -u postgres
psql

Now create a login role and a database that role owns. Run these inside the postgres=# prompt:

sql — psql prompt
CREATE ROLE myapp_user WITH LOGIN PASSWORD 'strong_password_here';
CREATE DATABASE myapp_db OWNER myapp_user;
GRANT ALL PRIVILEGES ON DATABASE myapp_db TO myapp_user;
\q

Type exit to leave the postgres user. Your Next.js app will connect using this string:

DATABASE_URL
postgresql://myapp_user:strong_password_here@localhost:5432/myapp_db
Give the role ownership of its own database rather than full SUPERUSER rights. It's all your app needs and keeps the database far safer.
3
Build

Install Node.js & Build the App

Install Node.js using fnm (Fast Node Manager) so you can manage versions cleanly, then activate the latest LTS:

bash — on your VPS
curl -fsSL https://fnm.vercel.app/install | bash
source ~/.bashrc
fnm install --lts
fnm use --lts
node -v

Clone your repository. Use the HTTPS URL for a public repo, or the SSH URL for a private one:

bash — on your VPS
# public repo
git clone https://github.com/username/your-nextjs-app.git

# private repo (SSH)
git clone git@github.com:username/your-nextjs-app.git

cd your-nextjs-app

Install dependencies, create your .env, and add the database URL plus any secrets your app uses:

bash — on your VPS
npm install
cp .env.example .env
nano .env
.env
DATABASE_URL="postgresql://myapp_user:strong_password_here@localhost:5432/myapp_db"
NODE_ENV=production

If you use an ORM like Prisma or Drizzle, run your migrations now, then create the production build:

bash — on your VPS
# example — Prisma
npx prisma migrate deploy

npm run build
Tip

A successful npm run build is your green light. If it fails here, fix it before going further — a broken build will never serve correctly behind the proxy.

4
Run

Keep It Running with PM2

If you just run npm run start, the app dies the moment you close your SSH session. PM2 keeps it alive in the background and restarts it automatically. Install it globally and start your app:

bash — on your VPS
npm install -g pm2
pm2 start npm --name "nextjs-app" -- run start

Next.js listens on port 3000 by default. Make PM2 relaunch your app after a server reboot:

bash — on your VPS
pm2 startup
pm2 save

Handy commands to manage it later:

  • pm2 list — see running apps and their status
  • pm2 logs nextjs-app — tail live logs
  • pm2 restart nextjs-app — restart after pulling new code
5
HTTPS

Serve It with Caddy (Automatic HTTPS)

Your app runs on port 3000, but visitors should reach it on https://yourdomain.com. Caddy is a web server that reverse-proxies traffic to your app and provisions a free SSL certificate automatically. Install it:

bash — on your VPS
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Open the Caddy config file:

bash — on your VPS
sudo nano /etc/caddy/Caddyfile

Replace its contents with this — it proxies your domain to the Next.js server and redirects www to the root domain:

/etc/caddy/Caddyfile
yourdomain.com {
    reverse_proxy localhost:3000
}

www.yourdomain.com {
    redir https://yourdomain.com{uri}
}

Save (Ctrl+O, Enter, Ctrl+X), then reload Caddy to apply it:

bash — on your VPS
sudo systemctl reload caddy
That's it. Caddy automatically fetches and renews a Let's Encrypt SSL certificate — no certbot, no cron jobs. Open https://yourdomain.com and your full-stack Next.js app is live and secured. 🎉

Shipping Updates Later

When you push new code to GitHub, redeploying on the VPS is three commands:

bash — on your VPS
git pull
npm install && npm run build
pm2 restart nextjs-app

Quick Troubleshooting

SymptomLikely fix
502 / Bad GatewayApp isn't running — check pm2 list and pm2 logs.
Site won't load at allDNS not pointed yet — re-check the A records with nslookup.
Database connection errorVerify DATABASE_URL credentials and that PostgreSQL is running.
No HTTPS / cert errorPorts 80 & 443 must be open in your VPS firewall for Caddy.

Wrapping Up

You've taken a full-stack Next.js app from a GitHub repo to a live, HTTPS-secured domain on your own server — DNS, PostgreSQL, Node.js, PM2 and Caddy, all wired together. The same pattern scales to almost any Node backend; swap the database or add an api.yourdomain.com block in the Caddyfile when you need it.

Want to learn this end to end with real projects and mentorship? Our Full Stack AI Bootcamp covers building and deploying production apps.

Discussion

Leave a Reply
Ready to Ship Real Apps?
Learn Full-Stack Deployment Hands-On

Join our live online classes and learn to build and deploy production-ready full-stack apps — from code to live server — with expert mentorship.

Explore the Bootcamp
Also check: /blog/deploy-mern-mysql-vps-coolify.php — MERN on a VPS