Guide

Deploy Next.js to your own server

Node 22, PM2, nginx and free SSL on any Ubuntu VPS, step by step. The automated route is at the end.

Prerequisites

A VPS with at least 1 GB RAM (2 GB is comfortable for builds), SSH access as root or a sudo user, your app in a git repository, and — for HTTPS — a domain with an A record pointing at the server's IP. The steps work for both the App and Pages routers with a plain next build (no static export).

Step 1Install Node.js 22

bash
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs git nginx
node -v   # v22.x

Step 2Clone and build

bash
cd /var/www
sudo git clone https://github.com/you/my-app.git
cd my-app
sudo npm ci
sudo npm run build

If the build gets killed on a 1 GB server, add temporary swap first:

bash
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile

Step 3Keep it running with PM2

PM2 restarts the app on crashes and on reboot. Next.js serves on port 3000 by default.

bash
sudo npm install -g pm2
pm2 start npm --name my-app -- start
pm2 startup systemd   # prints a command — run it
pm2 save

Step 4nginx reverse proxy

Create /etc/nginx/sites-available/my-app:

/etc/nginx/sites-available/my-app
server {
    listen 80;
    server_name example.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;
    }
}
bash
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 5Free SSL with certbot

bash
sudo snap install --classic certbot
sudo certbot --nginx -d example.com

Certbot rewrites the nginx config for HTTPS and installs an auto-renewing Let's Encrypt certificate. Your app is now live at https://example.com.

Step 6Redeploys

Each update is a pull, build and restart. Save this as deploy.sh:

deploy.sh
#!/usr/bin/env bash
set -e
cd /var/www/my-app
git pull
npm ci
npm run build
pm2 restart my-app

Note the trade-off: between pm2 restart and the app finishing boot there are a few seconds of downtime, and a failed build can leave the previous process serving a half-updated directory. Solving that properly means building in isolation and switching atomically — which is exactly what the automated route below does.

Gotchas worth knowing

  • Set output: "standalone" in next.config to shrink what has to exist on the server at runtime.
  • Environment variables: PM2 does not read .env.production automatically for npm start — Next.js does, at build and runtime, as long as the file sits in the app directory.
  • next/image optimization uses sharp — npm ci installs it, but on minimal distros you may need build tools (sudo apt-get install -y build-essential).
  • Open only ports 80/443 in your firewall; the Node port (3000) should stay internal.

Written for Ubuntu 22.04/24.04, Node 22 and Next.js 14/15, July 2026. Something off? Email [email protected] and we'll fix the guide.

The automated route

Same server. One push.

Pushify does all of the above on the same kind of server: each git push builds in isolation and goes live with a zero-downtime switch.