Proxy inverse PM2 + Nginx : relier une app Node.js au port 80
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.
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=noninteractiveSi 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 -yUne 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 nodejsSetting 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 -vv26.13.0npm -v11.20.0npm 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 ~/helloCré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');
APPL'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 pm2added 45 packages in 5spm2 -v[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2La 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 ~/helloPlacez-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/sonline 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 helloUse --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/spm2 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/sAprè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/sPM2 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.pm2pm2 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 rebootEnviron 2 minutes plus tard, reconnectez-vous en tant que deploy et vérifiez l'état de l'app.
uptime -pup 2 minutespm2 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/sDeux 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 nginxSetting 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 -vnginx 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/helloNginx ne lit que les configurations qui ont un lien dans le dossier sites-enabled.
🔲 nginx -t
sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsudo systemctl reload nginxVé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 4HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16Aprè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.
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 --nginxL'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
Journaux des services
Commande journalctl : utilisation, journaux par service et nettoyage de la taille du journal
Article expliquant comment lire, service par service, les journaux de services systemd comme nginx ou pm2-deploy.
ℹ️ 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.