TempsTemps
  • Docs
  • Blog
  • Roadmap
  • Pricing
  • Enterprise
  • Security
  • Contact
Star—
TempsTemps

Open-source deployment platform with built-in error tracking, analytics, and monitoring. Runs on any VPS. No surprise bills, no data leaving your infrastructure.

  • Product
  • Features
  • AI Agents
  • Error Tracking
  • Observability
  • Analytics
  • Documentation
  • Changelog
  • Enterprise
  • Contact
  • Resources
  • Getting Started
  • Upgrade
  • GitHub
  • Reddit
  • Tools
  • VPS Security Scanner
  • PaaS Tax Calculator
  • Compare
  • vs Vercel
  • vs Netlify
  • vs Coolify
  • All Platforms
  • Deploy
  • Next.js
  • Node.js
  • Django
  • Laravel
  • Go
  • Rust
  • All Frameworks →
  • Legal & Compliance
  • Security & Trust
  • Data Ownership & Privacy
  • GDPR Compliance

© 2026 Temps. All rights reserved.

GitHubDocs
Back to all posts

Deploy Next.js to a VPS the Hard Way — Every Step Other Tutorials Skip

Bare server to production URL with Linux, PM2, Nginx, and Let's Encrypt — plus the self-hosted Temps alternative. Includes monitoring gaps most VPS guides skip.

David Viejo

David Viejo

March 20, 2026 · 4mo ago

Free guide

The Vercel Exit Playbook

The step-by-step checklist our team uses to migrate apps off Vercel, Netlify, and Heroku — without downtime or lost env vars.

  • Pre-migration audit checklist (env vars, DNS, edge functions)
  • Zero-downtime DNS cutover sequence
  • Post-migration monitoring setup
  • Rollback plan if anything breaks

No spam. Unsubscribe anytime. Privacy policy

#next.js#deployment#vps#nginx#pm2#devops#tutorial#deploy nextjs vps#nextjs without vercel
Back to all posts

You can deploy a Next.js app to a VPS without Vercel by running Node.js + PM2 + Nginx on any $4–6/mo server — or by using a platform like Temps that handles all of that automatically from a single git push. This guide covers both paths so you can choose what fits your situation.

TL;DR: Manual deployment means managing Node.js, PM2, Nginx, Certbot, deploy scripts, and GitHub Actions — plus separate services for monitoring, error tracking, and analytics. A self-hosted deployment platform collapses all of that into one tool. Either way, this guide walks every step.


How to Deploy a Next.js App to a VPS

The fastest answer: SSH into a fresh Ubuntu server, install Node 20, clone your repo, run npm ci && npm run build, start the app with PM2, put Nginx in front for SSL, and wire up GitHub Actions to re-run those steps on every push to main.

The rest of this guide explains exactly how to do that, why each piece exists, and what it still leaves unsolved.


Manual vs. Platform: What You're Actually Choosing

Before diving into the steps, it helps to know what you're signing up for:

Manual (PM2 + Nginx)Temps (self-hosted)Vercel
Setup time30–60 min per app~15 min totalMinutes
CostVPS + your timeVPS + Temps freeFree tier → see pricing page
DeploymentsCustom deploy.sh + GitHub Actionsgit push auto-deploygit push
Zero-downtimePM2 cluster mode (manual)Built-in health checks + rollbackYes
SSLCertbot (manual renewal)AutomaticAutomatic
AnalyticsSeparate tool requiredBuilt-in (privacy-first)Separate tool
Error trackingSentry/separateBuilt-inSentry/separate
RollbackManual git revert + redeployOne-clickOne-click
Self-hostedYesYes (Apache 2.0)No
Vendor lock-inNoneNoneYes

Temps is a single Rust binary that provides the git-push deployment workflow plus built-in analytics, error tracking, session replay, uptime monitoring, and managed databases — free to self-host, or ~$6/mo on Temps Cloud (Hetzner cost + 30%, no per-seat fees, no bandwidth bills). If you want the manual approach for full control or learning, keep reading. If you want to skip straight to a platform, see how Temps handles Next.js deployments.


What You Need (Manual Path)

  • A VPS from any provider (Hetzner, DigitalOcean, Linode, Vultr). 2 GB RAM minimum for a Next.js app with a build step. 4 GB if you're running a database on the same box.
  • A domain name pointed at your server's IP address.
  • SSH access to the server.
  • A Next.js app that builds successfully with npm run build locally.

This guide assumes Ubuntu 22.04 or 24.04. Debian works too with minor differences.

Step 1: Set Up the Server

SSH into your new server:

ssh root@your-server-ip

Update packages and install the basics:

apt update && apt upgrade -y
apt install -y curl git ufw

Set up the firewall. Open SSH, HTTP, and HTTPS. Close everything else:

ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw enable

Create a non-root user (running everything as root is asking for trouble):

adduser deploy
usermod -aG sudo deploy

Copy your SSH key to the new user:

rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Log out and log back in as the deploy user from now on.

Free guide

The Vercel Exit Playbook

The step-by-step checklist our team uses to migrate apps off Vercel, Netlify, and Heroku — without downtime or lost env vars.

  • Pre-migration audit checklist (env vars, DNS, edge functions)
  • Zero-downtime DNS cutover sequence
  • Post-migration monitoring setup
  • Rollback plan if anything breaks

No spam. Unsubscribe anytime. Privacy policy

Step 2: Install Node.js

Don't install Node from apt. The version in Ubuntu's default repos is usually ancient. Use the NodeSource repository:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

Verify:

node --version  # Should be 20.x
npm --version

If your project uses a specific Node version (check .nvmrc or engines in package.json), match it here. Mismatched Node versions between local and server are one of the most common causes of "works on my machine" deployment failures.

Step 3: Clone and Build Your App

cd /home/deploy
git clone https://github.com/your-username/your-nextjs-app.git
cd your-nextjs-app
npm ci

npm ci instead of npm install. It installs from the lockfile exactly, which is what you want in production. npm install can resolve to different versions.

Set your environment variables:

cp .env.example .env.production
nano .env.production
# Fill in your production values: DATABASE_URL, API keys, etc.

Build:

npm run build

If this fails, fix it before continuing. Common issues: missing env vars that the build needs at compile time (Next.js bakes NEXT_PUBLIC_* variables into the client bundle during build), or native dependencies that need build tools (sudo apt install -y build-essential).

Step 4: Run the App with PM2

You need a process manager. If you just run npm start in your terminal and disconnect, the process dies. PM2 keeps it running, restarts it if it crashes, and manages logs.

sudo npm install -g pm2

Start your app:

pm2 start npm --name "nextjs-app" -- start

Check it's running:

pm2 status
pm2 logs nextjs-app

By default, Next.js starts on port 3000. Verify it's listening:

curl http://localhost:3000

Tell PM2 to start your app on server boot:

pm2 startup
pm2 save

The pm2 startup command prints a line you need to copy and run with sudo. Don't skip it, or your app won't survive a server reboot.

Free guide

The Vercel Exit Playbook

The step-by-step checklist our team uses to migrate apps off Vercel, Netlify, and Heroku — without downtime or lost env vars.

  • Pre-migration audit checklist (env vars, DNS, edge functions)
  • Zero-downtime DNS cutover sequence
  • Post-migration monitoring setup
  • Rollback plan if anything breaks

No spam. Unsubscribe anytime. Privacy policy

Step 5: Install and Configure Nginx

Nginx sits in front of your Next.js app. It handles SSL termination, serves static files, and proxies dynamic requests to your Node process.

sudo apt install -y nginx

Create a site config:

sudo nano /etc/nginx/sites-available/your-app
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1: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;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

The proxy_set_header Upgrade and Connection 'upgrade' lines are for WebSocket support. If your app uses real-time features, these headers are required.

At this point, your app should be accessible at http://yourdomain.com. No SSL yet.

Step 6: SSL with Let's Encrypt

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot modifies your Nginx config to add SSL, sets up automatic certificate renewal, and redirects HTTP to HTTPS.

Verify the renewal timer is active:

sudo systemctl status certbot.timer

Certificates expire every 90 days. Certbot's timer renews them automatically. If you skip this check and the timer isn't running, your site goes down in 3 months with an expired cert. It happens more often than you'd think.

Step 7: Set Up Deployments

Your app is live. Now you need a way to update it when you push new code.

Create /home/deploy/deploy.sh:

#!/bin/bash
set -e

cd /home/deploy/your-nextjs-app

echo "Pulling latest code..."
git pull origin main

echo "Installing dependencies..."
npm ci

echo "Building..."
npm run build

echo "Restarting..."
pm2 restart nextjs-app

echo "Done."
chmod +x /home/deploy/deploy.sh

Automating with GitHub Actions

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_IP }}
          username: deploy
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: /home/deploy/deploy.sh

Add SERVER_IP and SSH_PRIVATE_KEY to your repo's GitHub Secrets.

What This Doesn't Handle

The deploy script above has real gaps:

  • No health check. If the new build is broken, PM2 restarts the old process, but there's a window where requests fail.
  • No rollback. If the deploy breaks the app, you have to manually revert.
  • No preview environments. Every push to main goes straight to production.
  • Downtime during restart. PM2's restart kills the old process and starts the new one. There's a 1–3 second gap with 502 errors.

You can solve each of these individually (blue-green deploys with Nginx upstream toggling, a webhook server, PM2's cluster mode). But each solution adds complexity, and by the time you've built all of them, you've built a deployment platform. For a deeper look at how these platforms automate this pipeline, see how git-push deployments work under the hood.

Free guide

The Vercel Exit Playbook

The step-by-step checklist our team uses to migrate apps off Vercel, Netlify, and Heroku — without downtime or lost env vars.

  • Pre-migration audit checklist (env vars, DNS, edge functions)
  • Zero-downtime DNS cutover sequence
  • Post-migration monitoring setup
  • Rollback plan if anything breaks

No spam. Unsubscribe anytime. Privacy policy

Step 8: Monitoring (The Part Most Tutorials Skip)

Your app is deployed. How do you know it's still running tomorrow?

Logs: PM2 handles application logs. Rotate them or they'll fill your disk:

pm2 install pm2-logrotate

Uptime: You need something that pings your site and alerts you when it's down. Free options: UptimeRobot, Betterstack (free tier), or a cron job that curls your health endpoint. See our guide on building an uptime monitoring system for more depth.

Error tracking: When your app throws an unhandled exception in production, how do you find out? Sentry (free tier), LogRocket, or parsing PM2 logs manually. We covered the options in error tracking without Sentry.

Analytics: Google Analytics, Plausible, Umami, or similar. If you want to avoid third-party scripts entirely, see how to add web analytics without third-party scripts.

Each of these is a separate tool, a separate account, a separate dashboard.

The Full Stack of What You Just Built

Let's count:

Running on your server:

  1. Node.js (runtime)
  2. PM2 (process manager)
  3. Nginx (reverse proxy, SSL termination)
  4. Certbot (certificate renewal)
  5. Git (code delivery)
  6. GitHub Actions (build automation)

Still need but didn't set up: 7. Uptime monitoring (external service) 8. Error tracking (external service) 9. Analytics (external service) 10. Log management (PM2 + logrotate, or external service)

That's 6 things on your server and 4 external services for a single Next.js app.


The Alternative: Auto-Detected Builds with Temps

If you'd rather not manage Nginx, PM2, Certbot, and four separate observability tools, Temps is a self-hosted deployment platform that replaces all of it.

Three things Temps does that the manual approach doesn't:

  1. Nixpacks auto-detection. Point Temps at your GitHub repo. It detects next in package.json and builds your app automatically — no Dockerfile, no build scripts to maintain. It detects standalone output mode too, using node server.js instead of npx next start when appropriate.

  2. Health checks with automatic rollback. Temps checks your app every 5 seconds after deploy. It requires 2 consecutive successful responses before marking the deployment live. If the new version fails health checks within the 60-second error window, Temps rolls back to the previous deployment automatically. The manual PM2 approach has no equivalent.

  3. Built-in observability stack. Analytics, error tracking, session replay, and uptime monitoring are included — not separate accounts. Temps replaces PostHog/Plausible, Sentry, FullStory, and Pingdom alongside its deployment engine.

Self-host Temps on the same VPS (Apache 2.0, free):

curl -fsSL https://temps.sh/install.sh | sh
temps setup

Or use Temps Cloud — managed Hetzner servers at ~$6/mo (Hetzner cost + 30%, no per-seat fees, no bandwidth bills). Then connect your Next.js repo and push.


Or: Other Platforms Worth Knowing

  • Vercel — They built Next.js. Free tier is generous. Costs scale fast with traffic.
  • Coolify — Open-source, self-hosted. Handles Docker, Traefik proxy, SSL, and git-push deploys.
  • Dokploy — Open-source, simpler than Coolify, focused on minimal configuration.
  • Kamal — From the Rails team. Deploys Docker containers to any server over SSH with minimal abstraction.

Frequently Asked Questions

How do I deploy a Next.js app without Vercel?

You have two realistic paths. Platform: self-host Temps on your own VPS (Apache 2.0, free) or use Temps Cloud (~$6/mo), connect your repo, and get Nixpacks auto-detection, health-checked zero-downtime deploys, and built-in analytics/error tracking/uptime monitoring from a single git push — no PM2 config, Nginx file, or Certbot cron to maintain. Manual: provision a VPS (Hetzner, DigitalOcean, Linode), install Node.js, run npm ci && npm run build, keep the process alive with PM2, put Nginx in front for SSL via Certbot, and wire GitHub Actions to redeploy on push — that's the full walkthrough on this page. Both paths avoid Vercel's per-seat pricing and bandwidth bills entirely; which one you pick depends on whether you want a platform to manage the deploy pipeline (Temps) or to own every layer of the stack yourself (manual).

Can I deploy Next.js to a VPS without Docker?

Yes. The manual path in this guide (Node.js + PM2 + Nginx) runs the app directly without Docker. Temps also supports Dockerfile-based builds if you prefer containerization.

Do I need a Dockerfile to deploy Next.js with Temps?

No. Temps uses Nixpacks to auto-detect Next.js projects from package.json. If your repo has a next dependency, Temps detects it as a Next.js app and configures the build automatically. A Dockerfile is supported but not required.

What's the minimum VPS size for a Next.js app?

2 GB RAM is the practical minimum for a Next.js app that builds on the server. 1 GB servers frequently run out of memory during next build. If you're also running a database on the same box, use 4 GB.

Does Temps support Next.js App Router and Server Components?

Yes. Temps runs Next.js as a standard Node.js application. App Router, Server Components, API routes, and Server Actions all work as expected — there are no serverless function limitations or Edge runtime constraints.

How does Temps handle zero-downtime deployments for Next.js?

Temps runs health checks every 5 seconds after a new deployment starts, requiring 2 consecutive successful responses before routing traffic to the new version. If health checks fail within the 60-second error window, it rolls back to the previous version automatically.