Pourquoi self-héberger Nuxt sur un VPS ?
Avec Nitro, Nuxt génère un build portable (preset: node-server) qui démarre comme un simple serveur Node écoutant sur un port. Cette portabilité est idéale pour un VPS : vous bénéficiez du rendu côté serveur, des routes server/api et du cache de route sans dépendre d'un runtime edge propriétaire.
L'autohébergement supprime les quotas de fonctions et vous laisse régler la mise en cache, la compression et le nombre d'instances Node. Pour une application Vue 3 en production avec authentification, appels API serveur et SEO soigné, le VPS offre la stabilité d'un serveur persistant et un coût fixe, sans surprise de facturation à l'invocation.
Encore faut-il un serveur qui accepte un processus Node.js permanent : la plupart des hébergements mutualisés ne le permettent pas, ce qui exclut d'emblée le SSR et les routes serveur. Sur un VPS, vous choisissez aussi la version de Node.js, le reverse proxy (Nginx ou Caddy) et la gestion des certificats TLS, sans plafond artificiel sur le CPU ou la mémoire.
Bénéfices concrets d'un Nuxt autohébergé
- Routes serveur
server/apisans facturation par appel. - SSR et hydratation Vue 3 servis depuis un serveur Node persistant et stable.
- Build Nitro portable (
node-server) facile à conteneuriser et à reproduire. - Cache de route et règles
routeRules(SSR, SSG, ISR, SWR) maîtrisés côté serveur. - Plusieurs apps Nuxt sur un même VPS derrière Nginx, chacune sur son port interne (3000, 3001…).
- Variables d'environnement runtime (
runtimeConfig) gérées en clair sur votre serveur. - Contrôle total sur les en-têtes HTTP (CSP, HSTS, cache-control) via le middleware Nitro.
- Un site prégénéré par
nuxt generate, servi par Nginx seul, sans processus Node.js permanent.
SSR, SSG, SPA ou hybride : quel mode de rendu choisir ?
Faites défiler le tableau
| Mode | Rendu | Processus Node.js | Idéal pour |
|---|---|---|---|
| SSR (par défaut) | À la requête, côté serveur | Oui, supervisé par PM2 | Application dynamique, SEO, contenu personnalisé |
| SSG (`nuxt generate`) | Au build, en HTML statique | Non, Nginx sert `.output/public` | Blog, vitrine, contenu peu changeant |
| SPA (`ssr: false` + `nuxt generate`) | Dans le navigateur | Non, Nginx suffit | Tableau de bord interne, application derrière authentification |
| Hybride (`routeRules`) | Par route : SSR, prérendu, SWR, ISR | Oui, serveur Nitro | Application mêlant pages statiques et routes API |
Prérequis matériels et logiciels
Comme pour tout framework moderne, le build Nuxt consomme de la mémoire : visez 2 Go de RAM minimum (le build peut être réalisé en CI si votre VPS est plus modeste). Au runtime, 1 vCPU et 1 Go suffisent pour le serveur Nitro sous charge légère ; montez à 2 vCPU / 2 Go pour des applications avec de nombreuses routes server/api simultanées, et à 2 vCPU / 4 Go si plusieurs applications Nuxt partagent le VPS.
Côté versions, partez de Nuxt 4 : Nuxt 3 a atteint sa fin de vie le 31 juillet 2026 et ne reçoit plus ni correctif ni patch de sécurité. Le déploiement décrit ici est le même pour les deux versions, puisque c'est Nitro qui produit le serveur. Installez une ligne LTS de Node.js encore maintenue, Node.js 24 ou 26 (Node.js 20 est en fin de vie depuis le 30 avril 2026), via nvm ou une image node:24-alpine, ainsi que PM2 pour la supervision de processus. node-server est le preset que Nitro applique par défaut à un build de production : le déclarer dans nuxt.config.ts le rend simplement explicite. Un domaine pointé vers votre VPS est requis pour le certificat TLS. Ubuntu 22.04 ou 24.04 LTS reste la base recommandée.
Partir du template Nuxt Stack
Le template Nuxt Stack du Marketplace ServOrbit prépare un VPS Ubuntu 24.04 avec Node.js dans la ligne LTS courante du dépôt NodeSource, PM2 installé globalement, Nginx et PostgreSQL. Il vous dispense d'installer le runtime : les étapes ci-dessous commencent alors à la configuration du projet.
C'est un environnement d'exécution, pas une application avec un écran de connexion : on y entre en SSH (ssh root@<ip-du-vps>) pour déposer son code. Au premier accès, Nginx sert sa page par défaut sur le port 80 ; le virtual host de votre application, le certificat Let's Encrypt (Certbot n'est pas préinstallé) et votre code restent à mettre en place. PostgreSQL n'écoute qu'en local, avec l'authentification peer pour l'utilisateur système postgres (sudo -u postgres psql) : de quoi brancher une DATABASE_URL sur 127.0.0.1 sans exposer la base.
Déployer Nuxt étape par étape
Configurer le projet pour Node
Définissez le preset Nitro dans
nuxt.config.ts:export default defineNuxtConfig({ nitro: { preset: 'node-server' } })Déclarez vos secrets dans
runtimeConfigcôté serveur uniquement — jamais dansruntimeConfig.public. Sur le VPS, installez Node.js 24 LTS vianvm install 24 && nvm use 24 && nvm alias default 24, puis PM2 globalement :npm install -g pm2. Sur le template Nuxt Stack, Node.js et PM2 sont déjà en place.Builder l'application
Clonez le dépôt et installez les dépendances :
git clone https://git.votredomaine.com/votre-org/votre-app.git /srv/nuxt-app cd /srv/nuxt-app npm ci npm run buildNitro produit un dossier
.outputautonome contenantserver/index.mjset les assets statiques dans.output/public. Ce dossier est tout ce dont le VPS a besoin pour servir l'application en production — vous pouvez aussi l'assembler en CI et le transférer par rsync pour éviter le build sur le serveur.Créer le fichier d'environnement
Créez
/srv/nuxt-app/.envavec vos variables runtime. Toutes les variables préfixéesNUXT_écrasent les clés correspondantes deruntimeConfigau démarrage :NUXT_MY_SECRET_KEY=valeur-en-prod NITRO_HOST=127.0.0.1 NITRO_PORT=3000Ne commitez jamais ce fichier. Assurez-vous qu'il appartient à l'utilisateur qui fait tourner PM2 et qu'il est lisible uniquement par cet utilisateur (
chmod 600).Avec
NITRO_HOST=127.0.0.1, Nitro n'écoute que sur la boucle locale : seul Nginx le joint, et le port 3000 n'est pas exposé sur Internet.Lancer le serveur Nitro avec PM2
Démarrez l'application en mode cluster, en demandant à Node de charger le fichier d'environnement : Nuxt ne lit pas
.enven production, et la ligne de commande de PM2 n'a pas d'option dédiée.pm2 start /srv/nuxt-app/.output/server/index.mjs \ --name nuxt-app -i max \ --node-args="--env-file=/srv/nuxt-app/.env" pm2 save pm2 startup-i maxlance une instance par vCPU ; c'est ce mode cluster qui rend possibles les rechargements sans coupure de la dernière étape. La commandepm2 startupaffiche une ligne à copier-coller pour créer le service systemd. Exécutez-la avec les droits administrateur. Vérifiez que l'application est bien en ligne :pm2 statusdoit afficher l'étatonline.Configurer Nginx en reverse proxy
Créez
/etc/nginx/sites-available/nuxt-app:server { listen 80; server_name votredomaine.com www.votredomaine.com; # Assets hashés : cache long, servis directement location /_nuxt/ { root /srv/nuxt-app/.output/public; expires 1y; add_header Cache-Control "public, immutable"; } # Fichiers statiques publics location /favicon.ico { root /srv/nuxt-app/.output/public; } # Tout le reste → 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; } }Activez le site et testez la configuration :
ln -s /etc/nginx/sites-available/nuxt-app /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx.Activer le HTTPS avec Let's Encrypt
Installez Certbot et le plugin Nginx, puis générez le certificat :
apt install certbot python3-certbot-nginx certbot --nginx -d votredomaine.com -d www.votredomaine.comCertbot modifie automatiquement le bloc Nginx pour écouter sur le port 443 et forcer la redirection HTTP → HTTPS. Vérifiez que
X-Forwarded-Protoest bien transmis à Nitro — sans cela,useRequestHeaders()côté serveur et les redirections basées sur le protocole peuvent se tromper. Le renouvellement automatique est géré par le timer systemd de Certbot, vérifiable avecsystemctl status certbot.timer.Mettre à jour sans interruption de service
Pour une mise à jour, le rechargement PM2 (pas le redémarrage) garantit la continuité. Regroupez les commandes dans un script
/srv/nuxt-app/deploy.sh, queset -earrête à la première erreur, avant tout rechargement :#!/bin/bash set -e cd /srv/nuxt-app git pull origin main npm ci npm run build pm2 reload nuxt-appRendez-le exécutable (
chmod +x deploy.sh) et appelez-le depuis votre pipeline CI/CD par SSH. En mode cluster, PM2 remplace les instances une à une : la nouvelle version prend le relais avant que l'ancienne ne s'arrête. Pour un rollback, revenez sur le commit précédent, reconstruisez et rechargez. Si le build se fait en CI, transférez uniquement le dossier.outputpar rsync et rechargez sans reconstruire sur le serveur.
Variables d'environnement : public, privé et rechargement
Nuxt range sa configuration d'exécution sur deux niveaux. Les clés placées sous runtimeConfig.public sont envoyées au navigateur et restent lisibles côté serveur : URL d'API publique, identifiant de mesure d'audience. Les autres clés de runtimeConfig ne quittent jamais le serveur : clés secrètes, accès à la base de données.
Au démarrage, une variable d'environnement écrase la clé correspondante si elle suit la convention NUXT_<NOM>, ou NUXT_PUBLIC_<NOM> pour une clé publique : NUXT_PUBLIC_API_BASE remplace runtimeConfig.public.apiBase. Une valeur lue ailleurs, par exemple process.env.AUTRE_VARIABLE écrit dans nuxt.config.ts, n'est prise qu'au build et reste figée dans l'artefact.
En pratique, changer une URL d'API ou une clé ne demande donc pas de reconstruire : on modifie le .env sur le serveur puis on recharge PM2 (pm2 reload nuxt-app). Le même dossier .output peut ainsi servir la préproduction et la production avec deux fichiers d'environnement différents. Seule condition : faire charger ce fichier par le processus, puisque Nuxt ne lit plus .env une fois l'application construite — d'où l'option --env-file passée à Node lors du lancement avec PM2.
Optimiser le rendu avec routeRules
L'une des forces de Nuxt sur VPS est la granularité des stratégies de rendu page par page, sans changer d'infrastructure. Dans nuxt.config.ts, les routeRules permettent de mélanger SSR, SSG, ISR et SWR sur un seul déploiement Nitro.
Les pages statiques (blog, mentions légales, landing pages) peuvent être prégénérées au build avec prerender: true : Nitro les sert depuis le disque, sans Node. Les pages semi-dynamiques (listes de produits, tableaux de bord publics) profitent de swr: 60 pour un cache d'une minute invalidé en arrière-plan. Le SSR complet reste réservé aux pages personnalisées qui dépendent de la session ou du profil utilisateur.
Cette approche réduit la charge sur Node sans nécessiter un CDN payant ou un layer de cache externe — votre Nginx met en cache ce que Nitro prégénère.
Nitro server vs Docker vs Vercel/Netlify pour héberger Nuxt
Faites défiler le tableau
| Approche | Avantages | Contraintes |
|---|---|---|
| Nitro `node-server` + PM2 | Simple, léger, rechargement sans downtime, coût fixe VPS | Supervision à gérer, un seul serveur sans haute dispo native |
| Docker sur VPS | Isolation, reproductibilité, rollback par image, compose multi-apps | Image à bâtir et stocker, mémoire supplémentaire par conteneur |
| Vercel / Netlify | Zéro configuration, edge CDN mondial, preview par PR | Quotas de fonctions, coût variable, pas de contrôle sur l'infra |
| Kamal (déploiement zero-downtime) | Orchestre Docker et kamal-proxy, déploiement en une commande (`kamal deploy`) | Outil Ruby (gem ou image Docker), config initiale plus longue, adapté aux équipes |
Journaux : lire, faire tourner, croiser avec Nginx
PM2 centralise les sorties de l'application : pm2 logs nuxt-app suit le flux en direct (sortie standard et erreurs), pm2 logs nuxt-app --lines 100 affiche les dernières lignes. Les fichiers sont écrits dans ~/.pm2/logs/, un -out.log et un -error.log par application, suffixés du numéro d'instance en mode cluster.
Ces fichiers ne tournent pas d'eux-mêmes. Le module pm2-logrotate les fait tourner au-delà de 10 Mo et en garde 30 par défaut ; adaptez ces seuils à la taille de votre disque :
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7Quand le navigateur reçoit une 502 ou une 504, regardez d'abord du côté de Nginx : /var/log/nginx/error.log dit s'il n'a pas pu joindre 127.0.0.1:3000 (processus arrêté, mauvais port) ou si Nitro a mis trop de temps à répondre.
Dépannage : erreurs courantes en production
Error: listen EADDRINUSE: address already in use :::3000
Un processus tient déjà le port 3000. Identifiez-le avec ss -tlnp | grep 3000 et arrêtez l'instance PM2 orpheline : pm2 delete nuxt-app suivi d'un redémarrage propre. Évitez de lancer plusieurs instances sans load balancer — configurez plutôt le cluster_mode de PM2.
502 Bad Gateway dans Nginx
Nitro n'est pas démarré ou écoute sur le mauvais port. Vérifiez pm2 status et consultez les logs : pm2 logs nuxt-app --lines 50. Vérifiez aussi que la directive proxy_pass dans Nginx pointe bien sur http://127.0.0.1:3000 (et non localhost qui peut résoudre en IPv6 si le process écoute uniquement en IPv4).
useRuntimeConfig() retourne undefined
Les variables NUXT_* ne sont lues qu'au démarrage du processus Nitro : après une modification du .env, rechargez avec pm2 reload nuxt-app. Vérifiez que le fichier est bien chargé par --node-args="--env-file=…" et non exporté dans le shell courant, qui disparaît avec la session SSH. Si la valeur manque côté navigateur seulement, la clé doit être déclarée sous runtimeConfig.public et la variable préfixée NUXT_PUBLIC_.
Les routes server/api répondent 404 en production
Vérifiez que votre build inclut bien le dossier server/ dans .output. Un nitro.preset mal configuré (par exemple static au lieu de node-server) génère un site statique sans serveur. Reconstruisez avec npm run build après avoir vérifié nuxt.config.ts.
X-Forwarded-Proto ignoré, redirections en boucle
Si Nuxt redirige HTTP → HTTPS en boucle, c'est que le middleware de redirection voit http malgré le HTTPS côté client. Assurez-vous que Nginx transmet bien proxy_set_header X-Forwarded-Proto $scheme et que votre nuxt.config.ts ne force pas la redirection HTTPS en doublon avec Certbot.
Supervision et métriques avec PM2
Au-delà du simple pm2 status, PM2 expose un tableau de bord temps réel avec pm2 monit : CPU, mémoire, relances et logs des processus. Pour une application Nuxt sous charge, activez le mode cluster pour utiliser tous les vCPU disponibles :
pm2 start .output/server/index.mjs --name nuxt-app -i maxPM2 distribue automatiquement les requêtes entre les workers. Exportez ces métriques vers Prometheus et Grafana pour être alerté des redémarrages intempestifs — signe d'un bug ou d'une fuite mémoire à corriger avant qu'il n'affecte les utilisateurs.
La documentation officielle
Pour la configuration avancée et les changements de version, référez-vous à la documentation officielle de Nuxt (routeRules, runtimeConfig, modules), à la page Node.js de Nitro pour les variables NITRO_PORT et NITRO_HOST, et à la documentation du mode cluster de PM2. Ce guide couvre la mise en ligne sur VPS ; la documentation éditeur reste la référence pour les réglages fins.