Skip to main content

Deploy NestJS on Linux — production setup

TL;DR

Complete guide to deploying a NestJS application on Ubuntu 24.04 with PM2, nginx reverse proxy, and SSL.

Key facts

Topology
NestJS on Ubuntu 24.04

TL;DR

NestJS ships as a TypeScript project that compiles to plain JavaScript. A production deployment means building the project, running the compiled output under PM2 for process management, and fronting it with nginx for SSL termination and static asset handling.

Prerequisites

  • Ubuntu 24.04 LTS with sudo access
  • Node.js 22 LTS installed (via NodeSource or nvm)
  • A domain name with DNS pointing to your server
  • Your NestJS project in a Git repository

Step 1 — Build the project

Clone and build the NestJS application:

sudo mkdir -p /var/www/nestapp && sudo chown $USER:$USER /var/www/nestapp
cd /var/www/nestapp
git clone git@github.com:yourorg/nestapp.git .
npm ci
npm run build

The build command compiles TypeScript to the dist/ directory. Verify the output exists:

ls dist/main.js

Create a .env file with production values. NestJS uses @nestjs/config with ConfigModule, so ensure NODE_ENV=production and all required variables are present:

cp .env.example .env
# Edit .env with production database URLs, secrets, etc.

Step 2 — Health check endpoint

Add a health controller if you haven't already. NestJS provides @nestjs/terminus for standardised checks:

npm install @nestjs/terminus

Register a /health endpoint that verifies database connectivity and returns a 200 when the app is ready to serve traffic. PM2 and external monitors will hit this endpoint to confirm the process is alive.

Step 3 — PM2 process management

Install PM2 and create an ecosystem file:

sudo npm install -g pm2

Create ecosystem.config.js in the project root:

module.exports = {
  apps: [{
    name: 'nestapp',
    script: 'dist/main.js',
    instances: 'max',
    exec_mode: 'cluster',
    max_memory_restart: '1G',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    error_file: '/var/log/pm2/nestapp-error.log',
    out_file: '/var/log/pm2/nestapp-out.log',
    merge_logs: true,
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    wait_ready: true,
    listen_timeout: 10000,
    kill_timeout: 5000,
  }],
};

Setting wait_ready: true means PM2 waits for process.send('ready') before routing traffic. NestJS emits this when you call app.listen() — add it explicitly in main.ts:

await app.listen(process.env.PORT ?? 3000);
if (process.send) process.send('ready');

Start and persist:

sudo mkdir -p /var/log/pm2
pm2 start ecosystem.config.js --env production
pm2 startup systemd
pm2 save

Step 4 — nginx reverse proxy

sudo apt install -y nginx

Create /etc/nginx/sites-available/nestapp:

server {
    listen 80;
    server_name api.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;
        proxy_read_timeout 60s;
    }
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/nestapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 5 — SSL and firewall

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api.example.com
sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable

Monitoring

Use PM2's built-in monitoring with pm2 monit for real-time process stats. Install pm2-logrotate to prevent disk fill:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7

With Reflex

Connect your server to Reflex for continuous NestJS monitoring. Reflex tracks PM2 cluster health, memory consumption across workers, nginx error rates, and SSL certificate expiry. It can orchestrate zero-downtime pm2 reload cycles, verify the /health endpoint post-deploy, and auto-rollback on failure — all from a single dashboard. See How it works.