Deployment guide

Deploy a Nuxt Application on a VPS

Deploy on a VPS Cloud →

Tutorial

Deploy a Nuxt Application on a VPS

Development7 min read7 steps

Nuxt 3 and its Nitro engine produce a standalone Node.js artifact, perfectly suited for self-hosting on a VPS. This approach frees you from cloud function quotas and gives you full control over caching, HTTP headers, and per-page rendering strategies. This guide covers the complete setup: project configuration, build, process supervision with PM2, Nginx reverse proxy and HTTPS, through to troubleshooting the errors that block you in production.

Contents· Why Self-Host Nuxt on a VPS?1/8
  1. 01Why Self-Host Nuxt on a VPS?
  2. 02Concrete Benefits of a Self-Hosted Nuxt
  3. 03Hardware and Software Prerequisites
  4. 04Deploy Nuxt Step by Step
  5. 05Optimize Rendering with routeRules
  6. 06Nitro server vs Docker vs Vercel/Netlify for hosting Nuxt
  7. 07Troubleshooting: Common Production Errors
  8. 08Monitoring and Metrics with PM2

Why Self-Host Nuxt on a VPS?

With Nitro, Nuxt 3 generates a portable build (preset: node-server) that starts as a simple Node server listening on a port. This portability is ideal for a VPS: you benefit from server-side rendering, server/api routes, and route caching without depending on a proprietary edge runtime.

Self-hosting removes function quotas and lets you tune caching, compression, and the number of Node instances. For a production Vue 3 application with authentication, server API calls, and careful SEO, the VPS offers the stability of a persistent server and a fixed cost, with no per-invocation billing surprises.

Concrete Benefits of a Self-Hosted Nuxt

  • server/api server routes with no per-call billing.
  • SSR and Vue 3 hydration served from a persistent, stable Node server.
  • Portable Nitro build (node-server) easy to containerize and reproduce.
  • Route caching and routeRules rules (SSR, SSG, ISR, SWR) controlled on the server side.
  • Pooling of several Nuxt apps on a single VPS behind Nginx.
  • Runtime environment variables (runtimeConfig) managed directly on your own server.
  • Full control over HTTP headers (CSP, HSTS, cache-control) via Nitro middleware.

Hardware and Software Prerequisites

As with any modern framework, the Nuxt build consumes memory: aim for 2 GB of RAM minimum (the build can be run in CI if your VPS is smaller). At runtime, 1 vCPU and 1 GB are enough for the Nitro server under light load; go up to 2 vCPU / 2 GB for applications with many concurrent server/api routes.

Install Node.js 20 LTS via nvm or a node:20-alpine image, along with PM2 for process supervision. Configure nitro: { preset: 'node-server' } in nuxt.config.ts. A domain pointed to your VPS is required for the TLS certificate. Ubuntu 22.04 or 24.04 LTS remains the recommended base.

Deploy Nuxt Step by Step

  1. Configure the Project for Node

    Set the Nitro preset in nuxt.config.ts:

    export default defineNuxtConfig({
      nitro: { preset: 'node-server' }
    })

    Declare your secrets in runtimeConfig on the server side only — never in runtimeConfig.public. On the VPS, install Node.js 20 LTS via nvm install 20 && nvm use 20 && nvm alias default 20, then PM2 globally: npm install -g pm2.

  2. Build the Application

    Clone the repository and install dependencies:

    git clone https://git.yourdomain.com/your-org/your-app.git /srv/nuxt-app
    cd /srv/nuxt-app
    npm ci
    npm run build

    Nitro produces a self-contained .output folder containing server/index.mjs and static assets in .output/public. This folder is all the VPS needs to serve the application in production — you can also assemble it in CI and transfer it by rsync to avoid building on the server.

  3. Create the Environment File

    Create /srv/nuxt-app/.env with your runtime variables. All variables prefixed NUXT_ override the corresponding keys in runtimeConfig at startup:

    NUXT_MY_SECRET_KEY=production-value
    NITRO_HOST=0.0.0.0
    NITRO_PORT=3000

    Never commit this file. Make sure it belongs to the user running PM2 and is readable only by that user (chmod 600).

  4. Launch the Nitro Server with PM2

    Start the application and configure automatic startup:

    pm2 start /srv/nuxt-app/.output/server/index.mjs \
      --name nuxt-app \
      --env-file /srv/nuxt-app/.env
    pm2 save
    pm2 startup

    The pm2 startup command displays a line to copy-paste to create the systemd service. Run it with administrator rights. Verify the application is running: pm2 status should show the online state.

  5. Configure Nginx as a Reverse Proxy

    Create /etc/nginx/sites-available/nuxt-app:

    server {
        listen 80;
        server_name yourdomain.com www.yourdomain.com;
    
        # Hashed assets: long cache, served directly
        location /_nuxt/ {
            root /srv/nuxt-app/.output/public;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    
        # Static public files
        location /favicon.ico {
            root /srv/nuxt-app/.output/public;
        }
    
        # Everything else → Nitro
        location / {
            proxy_pass http://127.0.0.1:3000;
            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_cache_bypass $http_upgrade;
        }
    }

    Enable the site and test the configuration: ln -s /etc/nginx/sites-available/nuxt-app /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx.

  6. Enable HTTPS with Let's Encrypt

    Install Certbot and the Nginx plugin, then generate the certificate:

    apt install certbot python3-certbot-nginx
    certbot --nginx -d yourdomain.com -d www.yourdomain.com

    Certbot automatically modifies the Nginx block to listen on port 443 and force HTTP → HTTPS redirect. Verify that X-Forwarded-Proto is correctly forwarded to Nitro — without it, useRequestHeaders() on the server side and protocol-based redirects may behave incorrectly. Automatic renewal is managed by Certbot's systemd timer, checkable with systemctl status certbot.timer.

  7. Update Without Service Interruption

    For an update, PM2 reload (not restart) guarantees continuity:

    cd /srv/nuxt-app
    git pull
    npm ci
    npm run build
    pm2 reload nuxt-app

    PM2 starts the new version, waits for it to respond, then cuts the old one: zero downtime. For a rollback, revert to the previous commit, rebuild and reload. If the build happens in CI, transfer only the .output folder via rsync and reload without rebuilding on the server.

Optimize Rendering with routeRules

One of Nuxt's strengths on a VPS is the granularity of per-page rendering strategies, without changing infrastructure. In nuxt.config.ts, routeRules allow you to mix SSR, SSG, ISR, and SWR on a single Nitro deployment.

Static pages (blog, legal notices, landing pages) can be pre-generated at build time with prerender: true: Nitro serves them from disk, without Node. Semi-dynamic pages (product listings, public dashboards) benefit from swr: 60 for a one-minute cache invalidated in the background. Full SSR remains reserved for personalized pages that depend on session or user profile.

This approach reduces the load on Node without requiring a paid CDN or an external cache layer — your Nginx caches what Nitro pre-generates.

Nitro server vs Docker vs Vercel/Netlify for hosting Nuxt

Scroll the table

ApproachAdvantagesConstraints
Nitro `node-server` + PM2Simple, lightweight, zero-downtime reload, fixed VPS costSupervision to manage, single server without native high availability
Docker on VPSIsolation, reproducibility, rollback by image, multi-app composeImage to build and store, additional memory per container
Vercel / NetlifyZero configuration, global edge CDN, PR previewsFunction quotas, variable cost, no infrastructure control
Kamal (zero-downtime deployment)Orchestrates Docker + Traefik proxy, git push is enoughRuby required, longer initial config, suited for teams

Troubleshooting: Common Production Errors

Error: listen EADDRINUSE: address already in use :::3000
A process is already holding port 3000. Identify it with ss -tlnp | grep 3000 and stop the orphaned PM2 instance: pm2 delete nuxt-app followed by a clean restart. Avoid running multiple instances without a load balancer — configure PM2's cluster_mode instead.

502 Bad Gateway in Nginx
Nitro is not started or is listening on the wrong port. Check pm2 status and review the logs: pm2 logs nuxt-app --lines 50. Also verify that the proxy_pass directive in Nginx points to http://127.0.0.1:3000 (not localhost which may resolve to IPv6 if the process only listens on IPv4).

useRuntimeConfig() returns undefined on the server side
NUXT_* variables are only read when the Nitro process starts. If you modify .env after launch, reload: pm2 reload nuxt-app. Make sure you use --env-file at startup and not export variables in the current shell.

server/api routes return 404 in production
Check that your build includes the server/ folder in .output. A misconfigured nitro.preset (for example static instead of node-server) generates a static site without a server. Rebuild with npm run build after verifying nuxt.config.ts.

X-Forwarded-Proto ignored, redirect loop
If Nuxt redirects HTTP → HTTPS in a loop, it means the redirect middleware sees http despite the client-side HTTPS. Make sure Nginx forwards proxy_set_header X-Forwarded-Proto $scheme and that your nuxt.config.ts does not force HTTPS redirect twice alongside Certbot.

Monitoring and Metrics with PM2

Beyond simple pm2 status, PM2 exposes a real-time dashboard with pm2 monit: CPU, memory, restarts, and process logs. For a Nuxt application under load, enable cluster mode to use all available vCPUs:

pm2 start .output/server/index.mjs --name nuxt-app -i max

PM2 automatically distributes requests across workers. Pair PM2 with a tool like Grafana via pm2-prometheus for alerts on unexpected restarts — a sign of a bug or memory leak to fix before it affects users.

Deploy Your Nuxt with Full Autonomy

The ServOrbit Cloud VPS provides a template with Node.js, PM2, Nginx, and automatic SSL, ideal for hosting your Nuxt 3 applications with SSR and server routes.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab