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
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
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:
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.
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:
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;
}
}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
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:
#!/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.