How to deploy a Next.js app on a VPS
Self-host Next.js on your own server for full control and predictable cost. Learn to build, run with PM2, reverse-proxy with Nginx, and add HTTPS.
You don't need a managed platform to run Next.js. Self-hosting on a VPS gives you full control, no per-request pricing, and no vendor lock-in. The production Next.js server runs great behind Nginx with a process manager. Here's the complete deployment.
Step 1: Prepare the server
On a fresh Ubuntu VPS, update and add a deploy user:
sudo apt update && sudo apt upgrade -y
sudo adduser deploy && sudo usermod -aG sudo deploy
Install a current Node.js LTS:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install nodejs -y
Step 2: Get the code and build
Clone your repo, install dependencies, and create the production build:
git clone https://github.com/you/my-next-app.git
cd my-next-app
npm ci
npm run build
The build compiles your app into an optimized production bundle. Set production environment variables in a .env or .env.production file — things like your database URL and API keys — before building if they're needed at build time.
Step 3: Run the production server with PM2
Next.js has its own production server started with next start (via your npm start script). Keep it alive with PM2:
sudo npm install -g pm2
pm2 start npm --name my-next-app -- start
pm2 startup systemd
pm2 save
By default Next.js listens on port 3000. Confirm it's up:
pm2 status
curl http://localhost:3000
If you need a different port, set it: pm2 start npm --name my-next-app -- start -- -p 4000.
Step 4: Reverse-proxy with Nginx
Expose the app on ports 80/443 through Nginx rather than opening 3000 to the world. Install Nginx and create /etc/nginx/sites-available/my-next-app:
server {
listen 80;
server_name example.com www.example.com;
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Enable and reload:
sudo ln -s /etc/nginx/sites-available/my-next-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 5: Add HTTPS
Point your domain's A record at the VPS, then let Certbot handle TLS:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
Step 6: Firewall
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Serving images and caching
Next.js image optimization and server rendering benefit from real CPU and fast disk. On a NVMe VPS the build step and on-demand rendering are noticeably quicker than on slower storage. If your site is image-heavy, give it a bit more RAM so the Node process has headroom.
Updating deploys
Your update routine:
cd my-next-app
git pull
npm ci
npm run build
pm2 restart my-next-app
For zero-downtime, PM2 restarts are near-instant, and Nginx keeps serving until the new process is ready.
Optional: run it in Docker
If you prefer containers, add a Dockerfile using Next.js standalone output and run it with docker run or Compose, still behind Nginx or Caddy. Either way the front-end proxy pattern is the same.
Why self-host Next.js
You get predictable flat-rate pricing, no cold starts, full access to the filesystem and background processes, and the freedom to run whatever database and services you like alongside the app. On a Nxeon full-root NVMe VPS, a Next.js deploy is a one-time setup followed by four-line updates — your app, your server, your rules.