Skip to main content

Deploy Next.js on Ubuntu — production guide

TL;DR

Complete guide to deploying a production Next.js application on Ubuntu 24.04 with standalone output, PM2, nginx, and SSL.

Key facts

Topology
Next.js on Ubuntu 24.04

TL;DR

This guide covers deploying a self-hosted Next.js application on Ubuntu 24.04 using the standalone output mode, PM2 for process management, nginx for reverse proxy and static asset serving, and Certbot for SSL — a production-ready setup without Vercel.

Prerequisites

  • Ubuntu 24.04 LTS with root or sudo access (minimum 2 GB RAM for builds)
  • A domain name with DNS pointing to your server
  • Your Next.js project in a Git repository

Step 1 — System preparation and Node.js 20 LTS

Update the system and install Node.js 20 LTS:

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git build-essential

Install Node.js via NodeSource:

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

Create a deploy user:

sudo adduser deploy
sudo usermod -aG sudo deploy

Step 2 — Configure standalone output

In your next.config.js, enable standalone output mode. This creates a self-contained deployment bundle that includes only the dependencies your application needs:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
};

module.exports = nextConfig;

Step 3 — Deploy and build

sudo mkdir -p /var/www/nextapp
sudo chown deploy:deploy /var/www/nextapp
cd /var/www/nextapp
git clone git@github.com:yourorg/nextapp.git .
npm ci

Create your .env.production file with the required environment variables, then build:

cp .env.example .env.production
npm run build

Copy the static and public directories into the standalone output:

cp -r public .next/standalone/public
cp -r .next/static .next/standalone/.next/static

Step 4 — PM2 process management

Install PM2 and configure an ecosystem file:

sudo npm install -g pm2

Create ecosystem.config.js:

module.exports = {
  apps: [{
    name: 'nextapp',
    script: '.next/standalone/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    max_memory_restart: '1G',
    node_args: '--max-old-space-size=1536',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
      HOSTNAME: '0.0.0.0',
    },
    wait_ready: true,
    listen_timeout: 10000,
    kill_timeout: 5000,
  }],
};

Start and persist:

pm2 start ecosystem.config.js
pm2 startup systemd
pm2 save

Verify the app is running:

curl -I http://127.0.0.1:3000

Step 5 — nginx reverse proxy with static asset caching

sudo apt install -y nginx

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

upstream nextjs_upstream {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name example.com;

    gzip on;
    gzip_types text/plain application/json application/javascript text/css image/svg+xml;
    gzip_min_length 1000;

    location /_next/static {
        alias /var/www/nextapp/.next/static;
        expires 365d;
        access_log off;
        add_header Cache-Control "public, immutable";
    }

    location /public {
        alias /var/www/nextapp/public;
        expires 30d;
        access_log off;
    }

    location / {
        proxy_pass http://nextjs_upstream;
        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_read_timeout 120s;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/nextapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

Step 6 — SSL via Certbot and firewall

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

Step 7 — Health check and environment variables

Add a health check API route in your Next.js app at pages/api/health.ts:

export default function handler(req, res) {
  res.status(200).json({ status: 'ok', uptime: process.uptime() });
}

Verify from the server:

curl http://127.0.0.1:3000/api/health

With Reflex

Once deployed, connect Reflex for continuous monitoring of your Next.js stack. Reflex tracks PM2 process health, nginx error rates, SSL certificate expiry, memory usage, and disk thresholds. It orchestrates zero-downtime deploys with health-gated rollouts, and can auto-recover from crashes and memory spikes — replacing manual SSH maintenance with audited, repeatable playbooks. See How it works.