Skip to main content

Nuxt.js server crash — production recovery guide

TL;DR

How to diagnose and recover from Nuxt 3 Nitro server crashes in production self-hosted environments.

Key facts

Topic
Production error triage
Stack
Nuxt.js / Linux

TL;DR

A Nuxt 3 server crash means the underlying Nitro engine — the server runtime powering SSR, API routes, and middleware — has exited unexpectedly. Users see connection refused or 502 errors until the process restarts. Because Nitro runs as a standard Node.js process, crashes stem from unhandled exceptions in server routes, SSR rendering errors from undefined data, or Nitro worker process failures under memory pressure.

Common causes

  • Unhandled errors in server routes or middleware — a server/api/ handler throwing without a try/catch kills the Nitro process
  • SSR rendering errors — accessing undefined reactive data or calling browser APIs (window, document) during server-side rendering
  • Nitro worker memory exhaustion — large SSR payloads or memory leaks causing the process to exceed available RAM
  • Missing environment variables — Nitro accessing undefined process.env.NUXT_* values at runtime
  • Incompatible server plugins — a Nitro plugin throwing during initialisation prevents the server from starting

Diagnosis workflow

Check if the Nuxt process is running and review recent logs:

pm2 status
pm2 logs nuxt-app --lines 200

If using systemd:

systemctl status nuxt-app
journalctl -u nuxt-app --since "15 minutes ago" --no-pager

Look for unhandled exception stack traces, Cannot read properties of undefined, or SIGKILL from the OOM killer:

dmesg | grep -i "killed process"

Test that the built application starts cleanly:

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

Fixing Nitro error handling

Use h3's createError and defineEventHandler with proper error boundaries in your server routes:

// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')

  if (!id || !/^\d+$/.test(id)) {
    throw createError({ statusCode: 400, message: 'Valid user ID required' })
  }

  try {
    const user = await fetchUserFromDB(id)
    if (!user) {
      throw createError({ statusCode: 404, message: 'User not found' })
    }
    return user
  } catch (error) {
    if (error.statusCode) throw error
    console.error('Failed to fetch user:', error)
    throw createError({ statusCode: 500, message: 'Internal server error' })
  }
})

Add a global error handler in server/plugins/error-handler.ts:

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('error', (error, { event }) => {
    console.error(`[Nitro Error] ${event?.path || 'unknown'}:`, error)
  })
})

PM2 restart strategy

Configure PM2 to handle crashes gracefully with exponential backoff:

module.exports = {
  apps: [{
    name: 'nuxt-app',
    script: '.output/server/index.mjs',
    instances: 2,
    exec_mode: 'cluster',
    max_memory_restart: '512M',
    exp_backoff_restart_delay: 100,
    max_restarts: 15,
    min_uptime: '10s',
    env: {
      NUXT_HOST: '0.0.0.0',
      NUXT_PORT: 3000,
      NODE_ENV: 'production',
    },
  }],
}

Preventing SSR crashes

Guard against undefined data in components rendered on the server:

const { data: user } = await useFetch('/api/user')
const displayName = computed(() => user.value?.name ?? 'Guest')

Never access browser-only APIs without checking the execution context:

if (import.meta.client) {
  window.scrollTo(0, 0)
}

Where Reflex helps

Reflex monitors your Nuxt 3 Nitro process health continuously — tracking crash loops, memory trends, and restart frequency. When it detects a crash, Reflex restarts the process via PM2, verifies the application responds to health checks, and correlates the crash with recent deployments for faster root-cause identification. See How it works.