Skip to main content

How to self-host Nuxt 3 (deploy on your own server)

TL;DR

Complete guide to self-hosting Nuxt 3 with Nitro on your own Linux server using PM2, nginx, and SSL.

Key facts

Topology
Nuxt 3 on Linux

TL;DR

Nuxt 3 uses the Nitro server engine to produce a lightweight, portable Node.js server. Self-hosting means running nuxt build, deploying the .output directory to your server, and running the server entry point under a process manager. This gives you full control over infrastructure, data residency, and cost — without vendor lock-in.

Understanding the Nuxt 3 build output

Running npx nuxt build produces a .output directory containing:

  • server/index.mjs — the Nitro server entry point
  • server/chunks/ — server-side code split into chunks
  • public/ — static assets including the _nuxt/ directory with hashed client bundles

The server is entirely self-contained — no node_modules required at runtime (Nitro bundles dependencies during build).

Step 1 — Build locally or in CI

npm ci
npx nuxt build

Transfer the .output directory to your server:

rsync -avz .output/ deploy@server:/var/www/nuxt-app/.output/

Or build directly on the server after pulling from Git.

Step 2 — Run with PM2

cd /var/www/nuxt-app
node .output/server/index.mjs

For production, use PM2 for process management, clustering, and automatic restarts:

sudo npm install -g pm2

Create ecosystem.config.cjs:

module.exports = {
  apps: [{
    name: 'nuxt-app',
    script: '.output/server/index.mjs',
    cwd: '/var/www/nuxt-app',
    instances: 'max',
    exec_mode: 'cluster',
    max_memory_restart: '512M',
    env: {
      NODE_ENV: 'production',
      NUXT_HOST: '127.0.0.1',
      NUXT_PORT: 3000,
    },
  }],
}
pm2 start ecosystem.config.cjs
pm2 startup systemd
pm2 save

Step 3 — Environment variables

Nuxt 3 runtime config reads from NUXT_* prefixed environment variables:

NUXT_PUBLIC_API_BASE=https://api.example.com
NUXT_SECRET_KEY=your-secret-here
NUXT_HOST=127.0.0.1
NUXT_PORT=3000

These override values in nuxt.config.ts runtimeConfig at runtime without rebuilding.

Step 4 — nginx reverse proxy

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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 60s;
    }

    location /_nuxt/ {
        alias /var/www/nuxt-app/.output/public/_nuxt/;
        expires 365d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}
sudo ln -s /etc/nginx/sites-available/nuxt-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 5 — SSL with Let's Encrypt

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com

SSR vs static generation

Nuxt 3 supports both modes:

  • SSR (default) — pages rendered on each request, ideal for dynamic content
  • Static generation (npx nuxt generate) — pre-renders pages to HTML at build time, can be served from any CDN or nginx without Node.js

For hybrid approaches, use route rules to pre-render some pages while SSR-rendering others:

export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
    '/blog/**': { swr: 3600 },
    '/dashboard/**': { ssr: true },
  },
})

With Reflex

Once deployed, connect Reflex to monitor your self-hosted Nuxt 3 application continuously. Reflex tracks Nitro process health, memory usage, response times, and nginx error rates. It handles zero-downtime deploys via PM2 cluster reload, SSL certificate renewal monitoring, and automated crash recovery — giving you managed-platform reliability on your own infrastructure. See How it works.