How to Deploy a Node.js App to Production in 2025
Complete guide to deploying Node.js apps the right way. From zero to production with PM2, Nginx, SSL, monitoring, and auto-scaling.
What You'll Learn:
- ✓ Server setup on Vultr/DigitalOcean (with $500 free credits)
- ✓ Deploy your Node.js app with PM2
- ✓ Configure Nginx as reverse proxy
- ✓ Set up free SSL with Let's Encrypt
- ✓ Implement monitoring and logging
- ✓ Set up CI/CD with GitHub Actions
- ✓ Scale horizontally with load balancing
Prerequisites
Before we begin, make sure you have:
- A Node.js app ready to deploy (Express, Next.js, Nest.js, etc.)
- Your code in a Git repository (GitHub, GitLab, etc.)
- A domain name (optional but recommended)
- Basic command line knowledge
Step 1: Choose Your Hosting Provider
You have three main options. I'll show you how to deploy on all three, but I recommend starting with Vultr or DigitalOcean for simplicity.
Step 2: Create Your Server
On Vultr:
- Sign up with the $300 free credit link
- Click "Deploy New Instance"
- Choose:
- Type: Cloud Compute (Regular Performance)
- Location: Nearest to your users
- OS: Ubuntu 24.04 LTS
- Plan: $6/mo (1 vCPU, 1GB RAM) - good for starters
- Add your SSH key (or use password - but SSH is more secure)
- Click Deploy
On DigitalOcean:
- Sign up with the $200 free credit link
- Create a new Droplet
- Choose:
- OS: Ubuntu 24.04 LTS
- Plan: Basic ($6/mo - 1GB RAM)
- Datacenter: Nearest to your users
- Add your SSH key
- Create Droplet
Pro tip: Both Vultr and DigitalOcean bill hourly. If you mess up, just destroy the server and create a new one. It'll cost you pennies.
Step 3: Initial Server Setup
SSH into your server:
ssh root@your-server-ip
Update the system and install Node.js:
# Update system packages apt update && apt upgrade -y # Install Node.js 20.x (LTS) curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt install -y nodejs # Verify installation node --version # Should show v20.x.x npm --version # Should show 10.x.x # Install build essentials (needed for some npm packages) apt install -y build-essential
Step 4: Set Up a Non-Root User (Security Best Practice)
Never run your app as root. Create a dedicated user:
# Create user adduser nodeapp # Add to sudo group usermod -aG sudo nodeapp # Switch to new user su - nodeapp
Step 5: Clone Your App
# Install git if not already installed sudo apt install -y git # Clone your repository git clone https://github.com/yourusername/your-app.git cd your-app # Install dependencies npm install # Copy environment variables cp .env.example .env nano .env # Edit with your production values
Step 6: Install and Configure PM2
PM2 is a production process manager for Node.js. It keeps your app running, auto-restarts on crashes, and handles zero-downtime deployments.
# Install PM2 globally sudo npm install -g pm2 # Start your app with PM2 pm2 start app.js --name "my-app" # Or if you're using npm scripts: pm2 start npm --name "my-app" -- start # Set PM2 to start on system boot pm2 startup systemd # Copy and run the command it gives you # Save PM2 process list pm2 save
Useful PM2 commands:
pm2 list # Show all running apps pm2 logs # View logs pm2 restart my-app # Restart your app pm2 stop my-app # Stop your app pm2 delete my-app # Remove from PM2 pm2 monit # Real-time monitoring
Step 7: Install and Configure Nginx
Nginx acts as a reverse proxy, handling SSL, static files, and load balancing. It's much better than exposing Node.js directly to the internet.
# Install Nginx sudo apt install -y nginx # Create Nginx config for your app sudo nano /etc/nginx/sites-available/my-app
Add this configuration:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost:3000; # Your Node.js app port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
}Enable the site:
# Create symlink to enable site sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/ # Test Nginx config sudo nginx -t # Reload Nginx sudo systemctl reload nginx
Step 8: Set Up Free SSL with Let's Encrypt
Never deploy without HTTPS in 2025. Let's Encrypt is free and automatic:
# Install Certbot sudo apt install -y certbot python3-certbot-nginx # Get SSL certificate (Certbot will auto-configure Nginx) sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # Test auto-renewal sudo certbot renew --dry-run
Done! Your app is now running on HTTPS.
Step 9: Set Up Firewall
# Enable UFW firewall sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable # Check status sudo ufw status
Step 10: Set Up Monitoring and Logging
PM2 Monitoring:
# Install PM2 monitoring module pm2 install pm2-logrotate # Configure log rotation (keep logs under control) pm2 set pm2-logrotate:max_size 10M pm2 set pm2-logrotate:retain 7
Set Up Error Tracking (Optional but Recommended):
Use Sentry for error tracking. It's free for small projects:
npm install @sentry/node
In your app:
const Sentry = require("@sentry/node");
Sentry.init({
dsn: "your-sentry-dsn",
environment: "production"
});Step 11: Set Up CI/CD with GitHub Actions
Automate deployments so you can push code and it automatically deploys.
Create .github/workflows/deploy.yml in your repo:
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /home/nodeapp/your-app
git pull origin main
npm install
pm2 restart my-appAdd secrets to your GitHub repo:
- Go to Settings → Secrets → Actions
- Add
SERVER_IP,SERVER_USER, andSSH_PRIVATE_KEY
Now every push to main auto-deploys!
Step 12: Database Setup (If Needed)
Option 1: PostgreSQL on Same Server
# Install PostgreSQL sudo apt install -y postgresql postgresql-contrib # Create database and user sudo -u postgres psql CREATE DATABASE myapp; CREATE USER myappuser WITH PASSWORD 'secure_password'; GRANT ALL PRIVILEGES ON DATABASE myapp TO myappuser; \q
Option 2: Managed Database (Easier)
Both Vultr and DigitalOcean offer managed databases. They handle backups, updates, and scaling:
- Vultr Managed Database: Starting at $15/mo
- DigitalOcean Managed Database: Starting at $15/mo
Worth it if you don't want to manage backups manually.
Step 13: Performance Optimization
Enable Gzip in Nginx:
Edit /etc/nginx/nginx.conf:
gzip on; gzip_vary on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_comp_level 6;
Enable HTTP/2:
In your Nginx site config, change:
listen 443 ssl http2;
Add Caching Headers:
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}Step 14: Scaling Your App
Vertical Scaling (Easier):
Just upgrade your server to more CPU/RAM. On Vultr/DigitalOcean, you can resize with a few clicks. Downtime is usually just a minute or two.
Horizontal Scaling (Better):
Run multiple instances with PM2 cluster mode:
# Run app on all CPU cores pm2 start app.js -i max # Or specify number of instances pm2 start app.js -i 4
Common Issues and Solutions
App crashes immediately:
- Check PM2 logs:
pm2 logs - Verify environment variables are set correctly
- Make sure port 3000 (or your app port) isn't already in use
502 Bad Gateway error:
- App probably isn't running - check
pm2 list - Verify the proxy_pass port in Nginx matches your app
- Check Nginx logs:
sudo tail -f /var/log/nginx/error.log
SSL certificate won't install:
- Make sure your domain DNS is pointing to server IP
- Wait 5-10 minutes after changing DNS
- Verify ports 80 and 443 are open in firewall
Estimated Costs
| Configuration | Vultr | DigitalOcean | Good For |
|---|---|---|---|
| 1GB RAM, 1 vCPU | $6/mo | $6/mo | Small apps, side projects |
| 2GB RAM, 1 vCPU | $12/mo | $12/mo | Production apps, light traffic |
| 4GB RAM, 2 vCPU | $24/mo | $24/mo | Heavy traffic, APIs |
| + Managed Database | +$15/mo | +$15/mo | Recommended for production |
But remember: With Vultr's $300 free credit or DigitalOcean's $200 free credit, you can run for months without paying anything.
Conclusion
You now have a production-ready Node.js deployment with:
- ✓ Zero-downtime deployments with PM2
- ✓ HTTPS with free SSL certificates
- ✓ Nginx reverse proxy for performance
- ✓ Automated CI/CD deployments
- ✓ Monitoring and error tracking
- ✓ Ready to scale
Total setup time: 30-45 minutes for your first deployment. Future deploys take seconds with CI/CD.
Cost: $0 for the first few months if you use the free credits, then $6-24/month depending on traffic.