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.