Proxy inverse PM2 + Nginx : relier une app Node.js au port 80

Environnement de testFamille DebianUbuntu 24.04.5 LTScompte utilisateur standard
Exploitation de l'app Node.js et proxy inverse vérifiés

Sur un serveur Ubuntu 24.04, nous avons mis en place un proxy inverse Node.js avec PM2 et Nginx : PM2 se charge d'exécuter l'app, et Nginx transmet à l'app les requêtes qui arrivent sur le port 80. Nous avons aussi vérifié que PM2 relance l'app même si son processus est tué ou si le serveur redémarre.

Les blocs de résultats ci-dessous montrent la sortie des commandes exécutées avec l'utilisateur ordinaire deploy. Le domaine est remplacé par la valeur d'exemple example.com, et les requêtes ont été envoyées depuis le serveur avec un en-tête Host.

🎯 Points clés
🗂️ Parcours de la requête
📂 NginxProxy inverse
📄 CertbotCertificat HTTPS
📂 PM2Lancement et démarrage auto
📄 App Node.js
Exécuter l'app avec PM2 et la lancer au démarrage
Nginx transmet à l'app les requêtes du port 80

1️⃣ Installer Node.js

Node.js s'installe en ajoutant le dépôt NodeSource à apt, et nous avons choisi Node.js 26, la version LTS en novembre 2026.

🔲 NodeSource setup_26.x

export DEBIAN_FRONTEND=noninteractive

Si vous définissez cette valeur d'abord, aucune fenêtre ne demande de réglage pendant l'installation des paquets. L'option -E de la commande sudo ci-dessous transmet aussi cette valeur à la commande exécutée avec les droits root.

curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -
2026-11-07 16:08:16 - Repository configured successfully.
2026-11-07 16:08:16 - To install Node.js, run: apt install nodejs -y

Une fois que le script a ajouté le dépôt, vous pouvez installer Node.js avec apt comme n'importe quel paquet Ubuntu.

🔲 apt-get install nodejs

sudo -E apt-get install -y nodejs
Setting up libatomic1:amd64 (14.2.0-4ubuntu2~24.04.1) ...
Setting up nodejs (26.13.0-1nodesource1) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Processing triggers for man-db (2.12.0-4build2) ...

🔲 node -v, npm -v

node -v
v26.13.0
npm -v
11.20.0

npm est inclus dans le paquet nodejs, vous n'avez donc pas à l'installer à part.

2️⃣ App de test Node.js

Pour vérifier que PM2 et Nginx sont bien reliés, créez une app de test qui renvoie une ligne de texte quand elle reçoit une requête.

🔲 app.js

mkdir -p ~/hello

Créez un dossier hello dans votre dossier personnel, puis écrivez-y le fichier app.js.

cat > ~/hello/app.js <<'APP'
const http = require('http');
http.createServer((req, res) => {
  console.log(req.method, req.url);
  res.end('hello from node\n');
}).listen(3000, '127.0.0.1');
APP

L'app n'accepte les requêtes que sur le port 3000 de 127.0.0.1 : on ne peut donc pas l'ouvrir directement depuis l'extérieur du serveur. Nginx sera configuré pour recevoir les requêtes extérieures et les transmettre à ce port.

3️⃣ Exécution de l'app avec PM2 et démarrage automatique

PM2 est un gestionnaire de processus qui exécute les apps Node.js en arrière-plan et relance une app quand son processus s'arrête. Cette section configure aussi le démarrage automatique, qui relance l'app au démarrage du serveur.

🔲 npm install -g pm2

sudo npm install -g pm2
added 45 packages in 5s
pm2 -v
[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2

La première commande pm2 lance aussi le démon PM2, et la version de PM2 installée est la 7.1.2.

🔲 pm2 start

cd ~/hello

Placez-vous dans le dossier qui contient app.js, puis lancez l'app avec PM2.

pm2 start app.js --name hello
[PM2] Starting /home/deploy/hello/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17257    │ 0s     │ 0    │ online    │ 0%       │ 26.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

--name est le nom de l'app affiché dans la liste PM2, et les commandes suivantes désignent l'app par ce nom.

pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17257    │ 2s     │ 0    │ online    │ 0%       │ 58.5mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s

online dans la colonne status signifie que l'app tourne, et la colonne ↺ indique le nombre de fois où PM2 a redémarré l'app.

🔲 curl 127.0.0.1:3000

Si vous envoyez une requête depuis le serveur, la ligne de texte renvoyée par l'app s'affiche telle quelle.

curl -sS http://127.0.0.1:3000/
hello from node

🔲 pm2 restart

Après avoir modifié le code de l'app, redémarrez-la pour appliquer la modification.

pm2 restart hello
Use --update-env to update environment variables
[PM2] Applying action restartProcessId on app [hello](ids: [ 0 ])
[PM2] [hello](0) ✓
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17306    │ 0s     │ 1    │ online    │ 0%       │ 19.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s
pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17306    │ 2s     │ 1    │ online    │ 0%       │ 58.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s

Après le redémarrage, le pid est passé de 17257 à 17306 et la colonne ↺ est montée à 1.

🔲 kill -9

On peut provoquer l'arrêt brutal du processus de l'app avec kill -9, et pm2 pid affiche le numéro de processus de l'app.

kill -9 "$(pm2 pid hello)"

Vérifiez à nouveau la liste PM2 3 secondes plus tard.

pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17797    │ 3s     │ 2    │ online    │ 0%       │ 58.6mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 51.2% | ram usage: 35.8% | eth0: ⇓ 1kb/s ⇑ 1kb/s | eth1: ⇓ 19kb/s ⇑ 0mb/s | disk: ⇓ 728kb/s ⇑ 3.1mb/s

PM2 avait relancé l'app avec le nouveau pid 17797, elle était online, et la colonne ↺ était montée à 2.

🔲 pm2 save

pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2

pm2 save enregistre la liste des apps en cours d'exécution dans le fichier dump.pm2, et au démarrage PM2 relance les apps d'après cette liste.

🔲 pm2 startup

Pour que PM2 s'exécute au démarrage, enregistrez PM2 comme service systemd. La commande ci-dessous crée le service avec le nom du compte actuel et son dossier personnel.

sudo env PATH="$PATH" pm2 startup systemd -u "$(id -un)" --hp "$HOME"
ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill

[Install]
WantedBy=multi-user.target

Target path
/etc/systemd/system/pm2-deploy.service
Command list
[ 'systemctl enable pm2-deploy' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-deploy.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-deploy...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-deploy.service → /etc/systemd/system/pm2-deploy.service.
[PM2] [v] Command successfully executed.

env PATH est la partie qui permet à pm2, exécuté avec les droits root, de trouver le chemin de node du compte actuel, et -u et --hp sont le compte et le dossier personnel avec lesquels le service s'exécute. La ligne ExecStart du fichier de service est pm2 resurrect, qui restaure au démarrage la liste d'apps enregistrée.

systemctl status pm2-deploy --no-pager | head -n 5
○ pm2-deploy.service - PM2 process manager
     Loaded: loaded (/etc/systemd/system/pm2-deploy.service; enabled; preset: enabled)
     Active: inactive (dead)
       Docs: https://pm2.keymetrics.io/

enabled signifie que le service s'exécute au démarrage. pm2 startup enregistre seulement le service sans le lancer maintenant, d'où l'état inactive (dead), et le service s'exécute à partir du démarrage suivant.

🔲 pm2 list après redémarrage

sudo systemctl reboot

Environ 2 minutes plus tard, reconnectez-vous en tant que deploy et vérifiez l'état de l'app.

uptime -p
up 2 minutes
pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 976      │ 108s   │ 0    │ online    │ 0%       │ 63.0mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0.3% | ram usage: 35.1% | disk: ⇓ 4kb/s ⇑ 4kb/s

Deux minutes après le démarrage du serveur, l'app était déjà online depuis 108 secondes : PM2 l'avait relancée en même temps que le démarrage.

4️⃣ Configurer le proxy inverse Nginx

Comme l'app n'accepte les requêtes que sur 127.0.0.1:3000, Nginx reçoit les requêtes arrivant sur le port 80 et les transmet à l'app. Une telle configuration, où un serveur placé devant reçoit les requêtes et les transfère à un serveur situé derrière, s'appelle un proxy inverse.

🔲 apt-get install nginx

sudo -E apt-get install -y nginx
Setting up nginx-common (1.24.0-2ubuntu7.19) ...
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
Setting up nginx (1.24.0-2ubuntu7.19) ...
nginx -v
nginx version: nginx/1.24.0 (Ubuntu)

Le Nginx du dépôt Ubuntu 24.04 est en version 1.24.0, et son installation l'enregistre aussi pour qu'il s'exécute au démarrage.

🔲 /etc/nginx/sites-available/hello

Créez le fichier /etc/nginx/sites-available/hello et mettez-y le contenu suivant.

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

proxy_pass est l'adresse de l'app à laquelle les requêtes sont transmises. Les en-têtes Host, X-Real-IP, X-Forwarded-For et X-Forwarded-Proto sont des valeurs transmises pour que l'app connaisse le domaine de la requête d'origine, l'IP du visiteur et s'il s'agit de http ou de https.

proxy_http_version 1.1 et les en-têtes Upgrade et Connection sont nécessaires pour faire passer les connexions WebSocket jusqu'à l'app. Les en-têtes Upgrade et Connection ne sont plus transmis à l'app après un proxy, on les ajoute donc directement dans la configuration.

sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/hello

Nginx ne lit que les configurations qui ont un lien dans le dossier sites-enabled.

🔲 nginx -t

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

Vérifiez d'abord la syntaxe de la configuration avec nginx -t, puis, si elle est correcte, appliquez la nouvelle configuration avec reload sans arrêter Nginx.

🔲 Requête curl avec en-tête Host

curl -sS -i -H 'Host: example.com' http://127.0.0.1/ | head -n 4
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16

Après le rechargement de la configuration, la requête envoyée avec un en-tête Host a reçu la réponse de l'app. La ligne Server des en-têtes de réponse est nginx, et Content-Length 16 correspond à la taille de la ligne hello from node renvoyée par l'app (16 octets avec le saut de ligne). Pour se connecter depuis l'extérieur du serveur, le port 80 doit être ouvert dans le pare-feu.

🧭 Requêtes vers l'app Node.js et relance
Schéma explicatif
🧭 Situation
Parcours de la requête
🌐NavigateurRequête vers example.com
➜
🧱NginxPort 80, proxy_pass
➜
🟢App Node.js127.0.0.1:3000
Gestion des processus
⚙️systemdService pm2-deploy
➜
🔁PM2Liste d'apps dump.pm2
➜
🟢App Node.jshello
Nginx sur le port 80 reçoit la requête du navigateur, la transmet à 127.0.0.1:3000 indiqué dans proxy_pass et renvoie telle quelle la réponse de l'app.
Quand le processus de l'app s'arrête, PM2 relance aussitôt la même app. Après l'arrêt avec kill -9, pm2 list 3 secondes plus tard montrait l'app online avec un nouveau pid.
Au démarrage du serveur, systemd exécute le service pm2-deploy, et PM2 relance les apps d'après la liste enregistrée avec pm2 save.

5️⃣ Activer HTTPS avec Certbot

Avec le plugin Nginx de Certbot, vous pouvez obtenir le certificat et modifier la configuration Nginx en une seule fois. Lancez la commande ci-dessous une fois que le domaine pointe vers l'IP du serveur.

sudo certbot --nginx

L'installation de Certbot et le test du renouvellement automatique sont présentés dans l'article ci-dessous ; sa commande d'émission vise Apache, remplacez donc seulement l'étape d'émission par la commande ci-dessus.

🔢 FAQ & contenus recommandés

Quand Nginx ne parvient pas à joindre l'adresse de l'app indiquée dans proxy_pass, il renvoie 502 Bad Gateway. Après l'arrêt de l'app avec pm2 stop hello, la même requête a renvoyé 502, et /var/log/nginx/error.log contenait la ligne [connect() failed (111: Connection refused) while connecting to upstream] avec l'adresse visée, http://127.0.0.1:3000/. Vérifiez d'abord dans pm2 list que l'app est online, et que le port sur lequel l'app reçoit les requêtes correspond à l'adresse de proxy_pass. Après la relance de l'app, la même requête est revenue avec 200.

PM2 enregistre la sortie de l'app dans des fichiers nommés d'après l'app, dans le dossier .pm2/logs sous le dossier personnel du compte qui l'a lancée. pm2 logs hello --nostream --lines 5 a affiché les dernières lignes de deux fichiers, /home/deploy/.pm2/logs/hello-out.log et hello-error.log, et le fichier out contenait la requête GET / journalisée par l'app. Sans --nostream, la commande continue d'afficher les nouveaux journaux à mesure qu'ils arrivent.

Le service de démarrage automatique se supprime avec pm2 unstartup systemd. Après avoir ajouté ou retiré des apps, relancez pm2 save pour que la liste modifiée soit utilisée au démarrage. Lors de l'exécution de pm2 startup, ces deux commandes étaient aussi indiquées à la fin de la sortie.

ℹ️ Mention d'affiliation
Le contenu de ce site contient des liens d'affiliation. Lorsqu'un visiteur achète un produit ou un service via l'un de ces liens, le site perçoit une commission du vendeur. Le montant payé par l'acheteur(il baisse lors des promotions ↓), lui, n'augmente pas. Les prix, remises et stocks indiqués correspondent à la date de rédaction et peuvent différer ; vérifiez-les donc auprès du vendeur avant d'acheter. Les produits sont choisis et évalués selon nos propres critères, et les commissions n'influencent ni l'ordre de présentation ni le contenu des évaluations.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Prouvez que vous êtes humain : 5   +   8   =