Next.js + PM2 + Nginx: Production Setup on a VPS
Learn how PM2 and Nginx work together to run your Next.js app in production — with auto-restart on crash, zero-downtime reloads, and free SSL. And how DeployWise automates all of it in one click.
The production stack explained
Running Next.js in production on a VPS involves three layers:
The request flow is: Client → Nginx (443) → PM2 → Next.js (3000)
What is PM2 and why use it?
PM2 is the de facto standard process manager for Node.js apps in production. Without it, if your Next.js app crashes (unhandled exception, memory leak, etc.) it stays down until you manually restart it — potentially losing hours of uptime.
Manual setup (what DeployWise automates)
Here's what the manual setup looks like — so you understand what's happening under the hood when DeployWise deploys your app:
1. Install Node.js and PM2
# Install NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc # Install Node.js LTS nvm install --lts nvm use --lts # Install PM2 globally npm install -g pm2
2. Build and start your Next.js app
# Clone your repo git clone https://github.com/you/my-app.git /var/www/my-app cd /var/www/my-app # Install dependencies npm ci # Build for production npm run build # Start with PM2 pm2 start npm --name "my-app" -- start pm2 save # Enable auto-start on reboot pm2 startup
3. Install and configure Nginx
# Install Nginx sudo apt update && sudo apt install -y nginx # Create site config sudo nano /etc/nginx/sites-available/my-app
server {
listen 80;
server_name yourdomain.com www.yourdomain.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-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/my-app /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx
4. Install free SSL with Certbot
# Install Certbot sudo apt install -y certbot python3-certbot-nginx # Get SSL certificate (auto-configures Nginx) sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # Auto-renewal is set up automatically by Certbot
Useful PM2 commands
# View all running processes pm2 list # Stream live logs pm2 logs my-app # Restart (hard restart) pm2 restart my-app # Reload (zero-downtime) pm2 reload my-app # Stop process pm2 stop my-app # Delete process pm2 delete my-app # Monitor CPU/memory usage pm2 monit
Useful Nginx commands
# Test configuration syntax sudo nginx -t # Reload config (no downtime) sudo systemctl reload nginx # Restart Nginx sudo systemctl restart nginx # View error log sudo tail -f /var/log/nginx/error.log # View access log sudo tail -f /var/log/nginx/access.log
How DeployWise automates all of this
Every step above — NVM installation, PM2 setup, Nginx configuration, SSL — is executed automatically by DeployWise when you click Deploy. You never touch the terminal.
On subsequent deploys, DeployWise runs a zero-downtime update:
Common production gotchas
✅ Static export doesn't support API routes or SSR. Use output: 'standalone' for SSR apps on VPS.
✅ Run pm2 logs my-app to see the error. Most common cause: missing environment variables. Set them in a .env file or via PM2 ecosystem.config.js.
✅ Your Node.js process isn't running or isn't listening on the expected port. Check pm2 status and ensure the proxy_pass port matches your app's PORT env variable.
✅ The Next.js build may have used an incorrect NEXT_PUBLIC_URL. Ensure environment variables are set before the build step, not just at runtime.
Skip the manual setup entirely
DeployWise handles Node.js, PM2, Nginx and SSL automatically — from a clean dashboard. No terminal required.
Try DeployWise Free