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 pointserver/chunks/— server-side code split into chunkspublic/— 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.