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.