Aller au contenu

Configurer une application Node.js dans cPanel : Node.js Selector et Passenger, étape par étape

Configurez une application Node.js dans cPanel avec l'outil Setup Node.js App, c'est-à-dire le Node.js Selector de CloudLinux qui exécute votre code derrière Phusion Passenger. Vous allez créer l'application, installer ses dépendances dans son propre environnement, définir des variables d'environnement, la redémarrer depuis le shell, et prouver que le processus en marche est bien celui qui répond à votre URL.

Ce guide montre comment faire tourner une application Node.js sur un serveur cPanel avec l’outil Setup Node.js App de cPanel. Si vous avez déjà démarré une application à la main avec node server.js par SSH pour la retrouver morte le lendemain matin, voici le correctif supporté. Cet outil, c’est le Node.js Selector de CloudLinux, et il exécute votre application derrière Phusion Passenger, un serveur applicatif à qui Apache confie les requêtes correspondantes et qui démarre et surveille votre processus à votre place. Il n’apparaît que sur les serveurs sous CloudLinux où le sélecteur est installé, donc une machine cPanel sous AlmaLinux ordinaire n’affichera jamais l’icône, et l’étape 1 vous dit laquelle vous avez. À la fin, vous aurez une petite application Express qui répond sur une vraie URL, avec ses dépendances, ses variables d’environnement et ses redémarrages gérés de la façon supportée.

Ce que vous allez accomplir

  • Une réponse ferme à la question de savoir si cette fonction existe sur votre serveur, et une solution de rechange nommée si ce n’est pas le cas.
  • Une application Node.js créée avec une racine d’application, une URL d’application et un fichier de démarrage que vous comprenez au lieu de les deviner.
  • Des dépendances installées dans l’environnement propre à l’application, ce qui est de loin l’erreur la plus fréquente.
  • Des variables d’environnement que le processus en marche arrive vraiment à lire, prouvées par une requête plutôt que tenues pour acquises.
  • Un redémarrage que vous déclenchez depuis le shell, et une vérification qui vous confirme que le nouveau code est bien en ligne.

Prérequis

Il vous faut un compte cPanel avec accès SSH, et curl sur votre propre poste pour les vérifications de la fin. Les exemples utilisent CPUSER comme nom de compte cPanel et example.com comme domaine. Remplacez les deux.

Il vous faut un serveur sous CloudLinux avec le Node.js Selector installé. L’étape 1 le vérifie. CloudLinux est un système d’exploitation bâti sur la même base qu’AlmaLinux, auquel on a ajouté l’isolation par compte et des versions d’interpréteur par compte ; si la différence est nouvelle pour vous, voyez CloudLinux ou AlmaLinux ordinaire sur cPanel.

Vous n’avez besoin de root pour rien dans ce guide, sauf une vérification facultative à l’étape 1 et un chemin de journal dans la section de validation. Tout le reste s’exécute sous l’utilisateur du compte.

Une note sur les versions : les versions de Node.js que vous pouvez choisir sont celles que l’administrateur du serveur a installées, donc votre liste risque de ne pas contenir la version que fait tourner votre portable. Prenez la version majeure disponible la plus proche et testez au lieu de présumer.

Comment ça fonctionne

Rien ici n’exécute node dans un terminal qui doit rester ouvert. Apache reçoit la requête, voit que l’URL appartient à une application Passenger, et confie la requête à Passenger. Passenger démarre votre application à la première requête qui arrive, la garde en marche, et la redémarre quand vous le demandez. Votre application parle à Passenger par un socket que Passenger crée, pas par un port public.

Schéma d'une requête qui voyage du navigateur vers Apache puis Phusion Passenger, jusqu'au processus Node.js démarré par le Node.js Selector de CloudLinux

Trois champs du formulaire de création font tout le travail, et les confondre cause la plupart des pannes décrites dans cet article :

  • Application root est un chemin de répertoire relatif à votre répertoire personnel. Saisissez nodeapp et votre code habite dans /home/CPUSER/nodeapp.
  • Application URL est l’adresse publique, choisie comme un domaine plus un chemin facultatif. Prenez example.com, tapez api, et l’application répond à https://example.com/api.
  • Application startup file est le fichier que Passenger charge, écrit relativement à la racine d’application. Saisissez app.js et Passenger charge /home/CPUSER/nodeapp/app.js.

Deux comportements surprennent les gens, alors autant les dire franchement.

Passenger n’exécute pas votre script npm start. Il charge directement le fichier de démarrage avec le binaire Node de l’environnement de l’application. Une application dont le script start compile aussi des ressources, exécute des migrations ou exporte des variables se comportera ici autrement que sur votre portable, parce que rien de cette préparation n’a lieu. Déplacez ce travail dans une commande que vous lancez vous-même par SSH, ou tout en haut du fichier de démarrage.

Votre fichier de démarrage appelle toujours listen() normalement, mais le numéro de port cesse d’avoir de l’importance. Passenger remplace le socket auquel cet appel se lie, donc l’application se rattache au socket de Passenger et non à un port public. Écrivez process.env.PORT || 3000, ne construisez pas d’URL à partir du numéro de port et n’ouvrez pas un deuxième écouteur sur un port fixe. Phusion documente ce comportement dans la documentation de Passenger.

Étape par étape

  1. Vérifiez que la fonction existe sur ce serveur. Par SSH, sous l’utilisateur du compte :

    cat /etc/redhat-release
    ls -d /opt/alt/alt-nodejs* 2>/dev/null

    La première commande devrait afficher une ligne qui commence par CloudLinux release, suivie d’une version et d’un nom de code. La seconde devrait afficher un répertoire par version majeure de Node installée, par exemple /opt/alt/alt-nodejs20. Si la ligne de version dit AlmaLinux, Rocky ou CentOS et que la seconde commande n’affiche rien, le Node.js Selector n’est pas sur ce serveur, et vous aurez beau fouiller cPanel, l’icône n’apparaîtra pas. Avec root, vous pouvez confirmer depuis l’autre côté :

    cloudlinux-selector get --json --interpreter nodejs

    Cela affiche un JSON qui énumère les versions disponibles. Un command not found veut dire que le paquet du sélecteur n’est pas installé.

    Si la fonction manque, la solution de rechange qui marche consiste à faire tourner votre application comme un service sur un port lié à 127.0.0.1 et à mettre un proxy inverse devant, ce qui demande root sur le serveur. Node.js derrière un proxy inverse Nginx couvre cette forme-là. Demandez à votre hébergeur laquelle des deux il prend en charge avant de bâtir l’une ou l’autre.

  2. Créez l’application. Dans cPanel, ouvrez Software, puis Setup Node.js App, puis CREATE APPLICATION. Remplissez exactement ces champs :

    • Node.js version : la version choisie à l’étape 1, par exemple 20.
    • Application mode : Production.
    • Application root : nodeapp.
    • Application URL : domaine example.com, chemin api.
    • Application startup file : app.js.

    Cliquez sur CREATE. Un message vert confirme que l’application a été créée, et elle apparaît dans la liste avec le statut démarré. Regardez maintenant ce que l’outil a écrit pour vous :

    ls -a /home/CPUSER/nodeapp
    grep -rl PassengerAppRoot /home/CPUSER --include=.htaccess 2>/dev/null

    La première montre un répertoire tmp et d’ordinaire un .htaccess. La seconde affiche le chemin de chaque .htaccess qui porte des directives Passenger, c’est-à-dire le fichier qu’Apache lit pour acheminer votre URL. Ouvrez-le et vous y verrez PassengerAppRoot, PassengerBaseURI, PassengerNodejs et PassengerStartupFile, chacun correspondant à un champ que vous avez tapé. Si rien ne s’affiche, l’application n’a pas été créée, peu importe ce que l’écran a annoncé.

  3. Activez l’environnement et installez les dépendances. La page de détail de l’application affiche une commande vers le haut, dans une boîte cliquable qui la copie. Elle ressemble à ceci, avec votre nom de compte, votre racine d’application et votre version dedans :

    source /home/CPUSER/nodevenv/nodeapp/20/bin/activate && cd /home/CPUSER/nodeapp

    Lancez-la par SSH. Votre invite de commande gagne un préfixe qui nomme l’environnement, du genre (nodeapp:20). Ce préfixe est le signal que les commandes suivantes utilisent le Node de cette application et les node_modules de cette application. Confirmez-le :

    which node
    node -v

    Attendez-vous à /home/CPUSER/nodevenv/nodeapp/20/bin/node et à une version qui commence par v20. Si which node affiche /usr/bin/node ou rien du tout, la ligne d’activation n’a pas pris effet, et tout ce que vous installez maintenant atterrit au mauvais endroit. Corrigez cela avant de continuer. Une fois le préfixe visible dans l’invite, installez :

    npm init -y
    npm install express

    npm init -y affiche le package.json généré. npm install se termine par une ligne du genre added 69 packages in 2s. Si vous déployez un dépôt existant qui possède déjà un package-lock.json, lancez plutôt npm ci, parce qu’il installe exactement les versions nommées par le fichier de verrouillage et enlève celles qui traînent. Réservez npm install aux moments où vous ajoutez ou mettez à niveau une dépendance, puisque cette commande réécrit le fichier de verrouillage. Une plainte disant que npm ci can only install packages when your package.json and package-lock.json are in sync veut dire que le verrou est périmé : lancez npm install une fois, versionnez le verrou mis à jour, puis revenez à npm ci.

  4. Écrivez le fichier de démarrage. Créez /home/CPUSER/nodeapp/app.js avec ce contenu :

    'use strict';
    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      res.json({
        ok: true,
        greeting: process.env.APP_GREETING || 'unset',
        node: process.version
      });
    });
    
    app.get('/health', (req, res) => res.type('text/plain').send('ok'));
    
    // Temporaire : montre le chemin transmis à l'application, pour voir si vos
    // routes ont besoin du préfixe /api sur ce serveur. À retirer une fois su.
    app.use((req, res) => {
      res.status(404).json({ seenByApp: req.url, originalUrl: req.originalUrl });
    });
    
    const port = process.env.PORT || 3000;
    app.listen(port, () => {
      console.log('app started, listening via Passenger (port value ' + port + ')');
    });

    Le fourre-tout du bas est là exprès. Savoir si Passenger retire le préfixe /api avant que vos routes le voient dépend de la façon dont les directives ont été écrites pour votre domaine, et le fourre-tout répond à cette question sur votre propre serveur en une seule requête au lieu de vous laisser deviner. Gardez-le jusqu’à l’étape de validation, puis supprimez-le.

  5. Définissez une variable d’environnement. Passenger ne lit pas de fichier .env de lui-même, ce qui prend de court quiconque a l’habitude du développement local. Sur la page de l’application dans cPanel, trouvez la section des variables d’environnement, cliquez sur le bouton pour en ajouter une, saisissez le nom APP_GREETING et la valeur hello from cPanel, puis enregistrez. Si vous voulez aussi la prise en charge d’un fichier .env, ajoutez le paquet dotenv et chargez-le dans les premières lignes de app.js ; c’est votre code qui le fait, pas la plateforme.

    Le processus en marche lit son environnement une seule fois, au démarrage, donc une variable enregistrée ne change rien tant que l’application n’a pas redémarré. C’est l’étape suivante.

  6. Redémarrez depuis le shell. La page cPanel a un bouton de redémarrage. L’équivalent par SSH tient en une commande, et c’est celle à utiliser dans un script de déploiement :

    mkdir -p /home/CPUSER/nodeapp/tmp
    touch /home/CPUSER/nodeapp/tmp/restart.txt

    Les deux commandes n’affichent rien quand elles fonctionnent. Passenger surveille la date de modification de tmp/restart.txt dans la racine d’application. Quand cette date change, il arrête le processus courant et en démarre un neuf à la prochaine requête qui arrive. Il n’y a aucune sortie à lire, donc la preuve vient des vérifications ci-dessous.

    Schéma de la boucle de déploiement : activer l'environnement, installer les dépendances, définir les variables, faire touch tmp/restart.txt, puis vérifier avec curl et les journaux

Valider que ça fonctionne

Lancez ceci depuis votre propre poste, puis depuis le serveur. D’abord, les en-têtes :

curl -sI https://example.com/api

Attendez-vous à une ligne de statut HTTP/1.1 200 OK, ou HTTP/2 200 si le serveur a négocié HTTP/2, et à un en-tête X-Powered-By: Express. Cet en-tête est la partie utile : il ne peut venir que du processus Express, donc il écarte l’hypothèse d’Apache servant discrètement un fichier statique périmé depuis le même chemin. Un 404 ou un 403 sans X-Powered-By veut dire que la requête n’a jamais atteint votre application, et le point 3 du dépannage en est la cause probable.

Maintenant le corps de la réponse, qui prouve que la variable d’environnement est bien arrivée :

curl -s https://example.com/api

Attendez-vous à quelque chose de proche de {"ok":true,"greeting":"hello from cPanel","node":"v20.19.5"}. Un greeting à unset veut dire que la variable a été enregistrée mais que le processus n’a pas redémarré, donc reprenez l’étape 6. Vérifiez ensuite ce que l’application voit pour un sous-chemin :

curl -s https://example.com/api/does-not-exist

Le JSON renvoyé montre seenByApp, soit le chemin auquel vos routes sont confrontées. S’il indique /does-not-exist, définissez vos routes sans le préfixe /api. S’il indique /api/does-not-exist, incluez le préfixe. Supprimez le fourre-tout une fois la réponse connue, puis redémarrez de nouveau.

Confirmez qu’un vrai processus tourne, sur le serveur :

ps -u CPUSER -o pid,etime,cmd | grep -i node

Vous devriez voir au moins une ligne qui nomme soit le binaire Node de l’environnement, soit la racine d’application ; Passenger renomme les titres de processus, donc elle peut se lire du genre Passenger NodeApp: /home/CPUSER/nodeapp. La colonne etime donne le temps écoulé depuis le démarrage, et elle retombe à quelques secondes après un redémarrage, ce qui vous prouve qu’un touch a pris effet au lieu de vous y fier aveuglément.

Enfin, les journaux. Tout ce que votre application écrit sur la sortie standard ou la sortie d’erreur est capté par Passenger et écrit dans le journal d’erreurs d’Apache, avec toute défaillance au démarrage :

ls -lt /home/CPUSER/logs/ | head -5
ls -l /etc/apache2/logs/error_log /usr/local/apache/logs/error_log 2>/dev/null

La première commande liste les journaux par domaine que votre compte peut lire. La seconde montre quel chemin de journal Apache existe sur ce serveur, puisque les serveurs EasyApache 4 le gardent sous /etc/apache2/logs/ (habituellement un lien symbolique vers /var/log/apache2/) tandis que les versions plus anciennes utilisaient /usr/local/apache/logs/. Lire le journal d’erreurs au complet demande root. Sans root, la zone Metrics de cPanel a une page Errors qui montre les entrées récentes de votre compte, et c’est là qu’apparaît la ligne console.log de l’étape 4.

Dépannage

L’URL renvoie 500 ou 503 juste après la création de l’application. Le chemin du fichier de démarrage est faux par rapport à la racine d’application. Passenger le résout à partir de la racine d’application, donc une saisie de src/app.js veut dire /home/CPUSER/nodeapp/src/app.js. Lancez ls -l /home/CPUSER/nodeapp/app.js ; si le fichier n’est pas là, déplacez-le ou corrigez le champ sur la page de l’application. Le journal d’erreurs porte une ligne correspondante sur l’impossibilité de trouver le fichier.

Le journal dit Cannot find module 'express'. npm install a été lancé en dehors de l’environnement activé, donc les paquets sont allés à côté d’un autre Node. Vérifiez avec which node avant toute chose : il doit afficher le chemin sous nodevenv. Le correctif consiste à activer, vider le répertoire, réinstaller, puis redémarrer :

source /home/CPUSER/nodevenv/nodeapp/20/bin/activate && cd /home/CPUSER/nodeapp
rm -rf node_modules
npm ci
touch tmp/restart.txt

Un listage de répertoire ou une vieille page s’affiche à la place de votre application. L’URL d’application pointe vers un chemin qui existe aussi comme vrai dossier rempli de contenu. Apache sert le fichier qu’il trouve avant même que Passenger entre en jeu. Lancez ls -la /home/CPUSER/public_html/api et retirez ou renommez tout index.html ou index.php qui s’y trouve, en gardant le .htaccess qui porte les directives Passenger.

Vos changements de code n’apparaissent pas. L’ancien processus tourne encore, parce que modifier un fichier ne redémarre rien. Lancez touch /home/CPUSER/nodeapp/tmp/restart.txt, faites une requête, puis relancez la vérification ps et confirmez que etime est retombé. Faites du touch la dernière ligne de votre script de déploiement, quel qu’il soit.

Vos règles .htaccess personnalisées disparaissent. Le Node.js Selector est propriétaire de ce fichier et le réécrit quand l’application est modifiée ou recréée. Copiez-le avant de toucher à quoi que ce soit avec cp /home/CPUSER/nodeapp/.htaccess /home/CPUSER/nodeapp/htaccess.bak, puis réappliquez vos règles ensuite. Mieux encore, gardez les règles qui appartiennent au site dans le .htaccess de la racine de documents plutôt que dans celui de l’application.

Quand ce n’est pas la bonne approche

Le Node.js Selector est bâti pour des applications web pilotées par les requêtes sur un compte partagé, et il est honnête sur le peu qu’il fait d’autre.

Un travailleur de fond qui tourne en continu s’y prête mal. Passenger démarre un processus quand une requête arrive et arrête les processus inactifs pour libérer des ressources, donc un consommateur de file d’attente ou un ordonnanceur qui doit tourner sans interruption n’a pas sa place ici. Ceux-là veulent un serveur privé virtuel avec une unité systemd, ou une plateforme de conteneurs avec un type de processus travailleur.

Une application chargée en websockets s’y prête mal aussi. Passenger sait porter des connexions websocket, mais un compte cPanel partagé est plafonné par LVE, le conteneur de ressources par compte de CloudLinux, quant au nombre de processus et de processus d’entrée simultanés qu’il peut tenir. Quelques centaines de connexions de longue durée atteindront ces plafonds bien avant que votre code soit en cause.

La mémoire est la troisième frontière. Si le tas de votre application a besoin de plus que ce que permet la limite de mémoire physique du compte, le noyau arrête le processus, et le symptôme ressemble à des redémarrages aléatoires plutôt qu’à une erreur de mémoire insuffisante. Mesurez le tas sous trafic réel, comparez-le à la limite du compte, et passez à un serveur que vous contrôlez si les chiffres ne rentrent pas.

Pour les champs et les écrans eux-mêmes, la documentation de cPanel reste à jour à chaque version.

Si c’est le genre de travail que vous préféreriez ne plus jamais porter, c’est le quotidien d’Opérations Cloud — réservez une revue d’infrastructure.