The quickest production setup for a Node.js app in 2026 is simple: run the app on an internal port, put Nginx in front of it, and use PM2 to keep the process alive. That gives you restarts, logs, startup persistence, and SSL without overcomplicating the server.
What this setup looks like
- Your app runs on
127.0.0.1:3000or a similar local port - Nginx handles public traffic on ports
80and443 - PM2 restarts the app if it crashes and starts it again after a reboot
Step 1: Update the server
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx curl git
Step 2: Install Node.js
Use a current LTS release unless your project has a strong reason not to.
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
node -v
npm -v
Step 3: Install PM2
sudo npm install -g pm2
pm2 -v
Step 4: Upload or clone the app
cd /var/www
sudo mkdir -p node-app
sudo chown -R $USER:$USER node-app
git clone your-repository-url node-app
Step 5: Install dependencies and build
cd /var/www/node-app
npm install
npm run build
If your app does not have a build step, skip that command.
Step 6: Start the app with PM2
Use the real start command for your framework. These are common examples:
pm2 start npm --name node-app -- start
pm2 save
If your app needs a custom port:
PORT=3000 pm2 start npm --name node-app -- start
pm2 save
Step 7: Enable PM2 on reboot
pm2 startup systemd
PM2 will print a command. Run that command exactly, then save again:
pm2 save
Step 8: Configure Nginx as a reverse proxy
sudo nano /etc/nginx/sites-available/node-app
server {
listen 80;
server_name example.com www.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;
proxy_cache_bypass $http_upgrade;
}
}
sudo ln -s /etc/nginx/sites-available/node-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Step 9: Add HTTPS
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Step 10: Check logs and status
pm2 status
pm2 logs node-app
sudo tail -f /var/log/nginx/error.log
PM2 commands worth remembering
pm2 restart node-apppm2 stop node-apppm2 delete node-apppm2 logs node-apppm2 monit
Common mistakes
- Binding the Node app to a public IP instead of localhost
- Forgetting
pm2 save - Skipping the startup command
- Not forwarding the correct headers in Nginx
- Using
npm run devin production
When to choose this over Docker
If you want a small, understandable deployment with fewer moving parts, this setup is excellent. It is especially good for a VPS, a small SaaS, an internal tool, or a content platform that needs to ship fast.
Useful next reads
If this is your first server, harden it first with How to Secure a Fresh Ubuntu Server Before Going Live. If you are still deciding on the web server, compare both options in Nginx vs Apache in 2026: What Developers Should Really Choose.
Quick FAQ
Do I need PM2 if I use systemd?
Not always, but PM2 is faster to set up and more convenient for many Node.js apps.
Can Nginx serve a Next.js app this way?
Yes. The reverse proxy pattern is the same. Only the start command may change.
Should I expose port 3000 publicly?
No. Let Nginx handle public traffic and keep the Node.js process behind it.