4 min read

Deploying Node.js Applications (Nuxt + NestJS) on Ubuntu 24.04

We used AI while writing this content.

Supported Platforms

Ubuntu 24.04 LTS
Ubuntu 22.04 LTS
Debian 12+
Other Linux distributions ✅ (with adjustments)

Setup

1. Install Node.js and npm

Update your system and install Node.js:

sudo apt update
sudo apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

Verify installation:

node --version
npm --version

Tip: Node.js 20.x is the current LTS (Long Term Support) version. Adjust the version number as needed.

2. Install PM2 process manager

PM2 keeps your application running in the background and restarts it automatically on crashes or server reboots:

sudo npm install -g pm2

3. Create dedicated user

Running node-js from a user without root permission is much safer, since even if somehow the process is hijacked, the attacker gains only user-level permission. Therefore, we should create a dedicated user :

sudo useradd <app_user>
sudo passwd <app_user>

4. Clone and Build your application

Navigate to your deployment directory and clone your repository, then install dependencies:

sudo mkdir -p /var/www
cd /var/www
sudo mkdir -p <app-name>
cd <app-name>
sudo git clone https://github.com/yourusername/your-repo.git .
cd <app-git-name> #git clone creates a subfolder

For NestJS (Backend)

sudo npm install
npm run build

For Nuxt (Frontend)

cd frontend
sudo npm install
npm run build

This creates a .output directory with your production-ready application.

This compiles TypeScript to JavaScript in the dist directory.

5. Configure environment variables

Create a .env file in your project root:

cd .. # make sure you are at project root folder
nano .env

Add your production configuration:

NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
API_SECRET=your-secret-key

Do the same for frontend/ :

nano frontend/.env
# Backend API base URL
NUXT_PUBLIC_API_BASE=http://localhost:3010

MAPTILER_API_KEY="get_your_free_key_at_https://maptiler.com"

Security note: Never commit .env files to version control. Keep sensitive credentials secure.

If you are using Prisma as database :

# Generate Prisma Client
npx prisma generate

# Run migrations
npx prisma migrate deploy

6. Start application with PM2

Switch to the dedicated app user :

sudo chown -R <app_user>:<app_user_group> <full-app-folder-path> # grant adequate permission to the app user
su <app_user>

Create an ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: 'nuxt-frontend',
      script: '.output/server/index.mjs',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      }
    },
    {
      name: 'nestjs-backend',
      script: 'dist/main.js',
      env: {
        NODE_ENV: 'production',
        PORT: 3001
      }
    }
  ]
};
pm2 start ecosystem.config.js

7. Configure PM2 to start on system boot

Switch back to a user with sudo permission

su <sudo-user>
pm2 startup systemd
pm2 save

Follow the command output instructions (you may need to run a generated command with sudo).

8. Set up Nginx as reverse proxy

Install Nginx:

sudo apt install -y nginx

Create a new Nginx configuration:

sudo nano /etc/nginx/sites-available/myapp

Add the following configuration:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    # Frontend (Nuxt)
    location / {
        proxy_pass http://localhost:3000;
        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;
    }

    # Backend API (NestJS)
    location /api {
        proxy_pass http://localhost:3001;
        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;
    }
}

Enable the site and restart Nginx:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

9. Configure firewall

sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable

Test connection from another machine:

curl http://<server-ip-address>

You should see the nginx welcome page. You do not see your actual site content since nginx is built for multi-site hosting and you must specify the web domain name in your request header to resolve to the actual site.

See guide on uncomplicated firewall if the site is not accessible from the public internet

If the server is now publically available via HTTP, add the following DNS records in your domain name configuration through your DNS provider :

; A records (replace <SERVER_IPV4> with your server IP)
@       IN  A   <SERVER_IPV4>
www     IN  A   <SERVER_IPV4>

; Optional IPv6
@       IN  AAAA  <SERVER_IPV6>
www     IN  AAAA  <SERVER_IPV6>

; Allow Let's Encrypt to issue certificates
@       IN  CAA 0 issue "letsencrypt.org"

Install Certbot:

sudo apt install -y certbot python3-certbot-nginx

Obtain and install SSL certificate:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Follow the prompts. Certbot will automatically configure Nginx for HTTPS.

Useful PM2 commands

Monitor applications:

pm2 status
pm2 logs
pm2 monit

Restart applications:

pm2 restart all
pm2 restart nuxt-app

Stop applications:

pm2 stop all
pm2 delete all

Updating your application

cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart all

Copyleft Statement

Renoncé du droit d'auteur

Much of our content is freely available under the Creative Commons BY-NC-ND 4.0 licence, which allows free distribution and republishing of our content for non-commercial purposes, as long as Ronzz.org is appropriately credited and the content is not being modified materially to express a different meaning than it is originally intended for. It must be noted that some images on Ronzz.org are the intellectual property of third parties. Our permission to use those images may not cover your reproduction. This does not affect your statutory rights.

Nous mettons la plupart de nos contenus disponibles gratuitement sous la licence Creative Commons By-NC-ND 4.0, qui permet une distribution et une republication gratuites de notre contenu à des fins non commerciales, tant que Ronzz.org est correctement crédité et que le contenu n'est pas modifié matériellement pour exprimer un sens différent que prévu à l'origine.Il faut noter que certaines images sur Ronzz.org sont des propriétés intellectuelles de tiers. Notre autorisation d'utiliser ces images peut ne pas couvrir votre reproduction. Cela n'affecte pas vos droits statutaires.