All articles
InfrastructureApril 12, 20268 min read

Deploying NestJS to a Linux VPS with PM2 and Nginx

Getting a NestJS application running in production involves more than copying files. Here's the exact process I use — from build to SSL.

NestJSPM2NginxLinuxDeployment

Getting a NestJS application running reliably in production on a Linux VPS involves more than just copying files. You need process management, a reverse proxy, and SSL. This is the exact setup I've used across multiple production deployments.

Prerequisites

  • A Linux VPS running Ubuntu 22.04 or later
  • Node.js 20+ installed on the server
  • A domain name pointing to your server's IP
  • SSH access with sudo privileges

1. Build the application locally

Always build locally and transfer the compiled output — never build on the production server. This keeps the server lean and your build process reproducible.

npm run build
# Outputs compiled JS to dist/

2. Transfer files to the server

Use rsync to transfer only what the server needs. Skip node_modules — install production dependencies directly on the server.

rsync -avz --exclude node_modules \
  dist/ package.json package-lock.json \
  user@your-server:/var/www/myapp/

On the server, install only production dependencies:

cd /var/www/myapp
npm install --omit=dev

3. Configure PM2

PM2 manages your Node.js process — it restarts the app on crash, persists across server reboots, and centralizes logs.

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp-api',
    script: './dist/main.js',
    instances: 1,
    autorestart: true,
    env: { NODE_ENV: 'production', PORT: 3001 },
  }],
};
pm2 start ecosystem.config.js
pm2 save
pm2 startup

4. Configure Nginx as a reverse proxy

server {
    listen 80;
    server_name api.yourdomain.com;
    location / {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

5. SSL with Certbot

apt install certbot python3-certbot-nginx
certbot --nginx -d api.yourdomain.com
NOTE

For zero-downtime deployments, use `pm2 reload myapp-api --update-env` instead of `pm2 restart`.