Déployer une application sur un VPS, ce n'est pas une seule tâche, mais une dizaine de petites étapes qui doivent s'enchaîner dans le bon ordre. Oubliez-en une et vous vous retrouvez avec un 502 Bad Gateway ou un Permission deniedsans savoir d'où il vient.
Ce guide déroule la procédure de bout en bout : on part d'un serveur fraîchement acheté et on termine sur une application servie en HTTPS, supervisée par PM2, et redéployée automatiquement à chaque git push grâce à GitHub Actions.
Ce dont vous avez besoin
- Un VPS sous Ubuntu (22.04 ou 24.04), avec son adresse IP et le mot de passe
rootfournis par l'hébergeur. - Un nom de domaine dont l'enregistrement DNS
Apointe vers l'IP du VPS. - Un dépôt GitHub contenant le code du projet.
- Un PC Windows avec OpenSSH (inclus par défaut sur Windows 10/11).
Créer un alias SSH pour se connecter facilement au serveur
Objectif : remplacer la saisie de l'IP et du mot de passe par une seule commande mémorisable (ssh mon-vps) et une connexion par clé, bien plus sûre.
1Se placer dans le dossier de configuration SSH
Sous Windows, le dossier .sshse trouve à la racine du profil utilisateur. On s'y rend depuis PowerShell.
cd C:\Users\Jordane\.sshS'il n'existe pas encore, créez-le avec mkdir C:\Users\Jordane\.ssh.
2Générer la paire de clés SSH
On génère une clé RSA de 4096 bits. L'option -C ajoute un commentaire (pratique pour identifier la clé plus tard) et -f fixe le nom du fichier de sortie.
ssh-keygen -t rsa -b 4096 -C "vps personnel" -f mon-vpsÀ la question passphrase, vous pouvez valider à vide (Entrée). Deux fichiers apparaissent : mon-vps (clé privée, à garder secrète) et mon-vps.pub (clé publique, à diffuser).
3Déclarer l'alias dans le fichier de configuration SSH
Toujours dans .ssh, on édite (ou on crée) le fichier config, sans extension. Il associe un nom court à une IP, un utilisateur et une clé.
# Mon VPS personnel
Host mon-vps
HostName 147.x.x.x
User root
IdentityFile ~/.ssh/mon-vps4Afficher la clé publique
On affiche le contenu de la clé publique pour le copier à l'étape suivante.
type mon-vps.pub5Installer la clé publique sur le serveur
On se connecte une première fois avec le mot de passedu VPS (celui fourni par l'hébergeur), puis on installe la clé publique.
ssh mon-vpsUne fois connecté au serveur, on exécute :
# 1. Créer le dossier .ssh s'il n'existe pas
mkdir -p ~/.ssh
# 2. Ouvrir le fichier authorized_keys
nano ~/.ssh/authorized_keys
# -> coller le contenu de mon-vps.pub
# -> enregistrer avec Ctrl+O puis Entrée, quitter avec Ctrl+X
# 3. Sécuriser les permissions (SSH refuse des droits trop larges)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys6Vérifier la connexion sans mot de passe
On se déconnecte, puis on relance la connexion : elle doit désormais s'ouvrir sans demander de mot de passe.
exit
ssh mon-vpsDurcir la sécurité du serveur
Objectif : un VPS exposé sur Internet est scanné en permanence. On crée un utilisateur non-root, on coupe l'accès par mot de passe et on ferme tous les ports inutiles.
1Créer un utilisateur non-root avec droits sudo
Travailler en rootau quotidien est risqué : la moindre erreur s'exécute avec tous les pouvoirs. On crée un utilisateur dédié, ici deploy.
# Créer l'utilisateur (un mot de passe sera demandé)
adduser deploy
# Lui donner les droits d'administration
usermod -aG sudo deploy
# Copier la clé SSH autorisée vers le nouvel utilisateur
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keysMettez à jour l'alias côté Windows pour utiliser ce compte (User deploy au lieu de User root dans .ssh/config), puis testez ssh mon-vps.
2Configurer le pare-feu UFW
UFW ne laisse passer que ce qu'on autorise explicitement : SSH, HTTP et HTTPS. Tout le reste est bloqué.
# Autoriser SSH, HTTP et HTTPS
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
# Activer le pare-feu
sudo ufw enable
# Vérifier les règles actives
sudo ufw status verbose3Désactiver la connexion root et par mot de passe
On édite la configuration du serveur SSH pour n'autoriser que l'authentification par clé.
sudo nano /etc/ssh/sshd_configRepérez et ajustez ces trois directives :
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yesPuis on recharge le service SSH pour appliquer :
sudo systemctl restart ssh4Installer fail2ban (optionnel mais recommandé)
fail2ban surveille les journaux et bannit automatiquement les adresses IP qui multiplient les tentatives de connexion échouées.
sudo apt install fail2ban -y
# Vérifier qu'il tourne
sudo systemctl status fail2banPréparer l'environnement applicatif
Objectif : installer la pile qui fera tourner et servira l'application. Système à jour, Nginx, Certbot, Node.js 20 et PM2.
1Mettre le serveur à jour
# Mettre à jour la liste des paquets disponibles
sudo apt update
# Installer les mises à jour
sudo apt upgrade -y
# Nettoyer les paquets devenus inutiles
sudo apt autoremove -y
sudo apt autoclean2Installer Nginx
Nginx jouera le rôle de reverse proxy: il reçoit le trafic web et le transmet à l'application Node.js (section VIII).
sudo apt install nginx -y
# Vérifier que le service est actif
sudo systemctl status nginxÀ ce stade, ouvrir http://147.x.x.xdans un navigateur doit afficher la page d'accueil par défaut de Nginx.
3Installer Certbot (certificats SSL gratuits)
Certbot obtient et renouvelle les certificats Let's Encrypt. Le paquet python3-certbot-nginxajoute l'intégration directe avec Nginx.
sudo apt install certbot python3-certbot-nginx -y
# Vérifier l'installation
certbot --version4Installer Node.js 20
Le dépôt officiel NodeSource fournit une version récente et maintenue, contrairement à celle des dépôts Ubuntu.
# Ajouter le dépôt officiel NodeSource pour Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
# Installer Node.js (npm est inclus)
sudo apt install -y nodejs
# Vérifier les versions
node -v
npm -v5Installer et configurer PM2
PM2 est un gestionnaire de processus : il maintient l'application en vie, la relance si elle plante et la redémarre au boot du serveur.
# Installer PM2 globalement
sudo npm install -g pm2
# Générer le script de démarrage automatique
pm2 startup
# Vérifier la version
pm2 -vMettre en place l'architecture des dossiers
Objectif : un emplacement standard et prévisible pour le code, que le pipeline CI/CD retrouvera toujours au même endroit.
La convention sous Linux est de servir les sites depuis /var/www. On y crée un dossier pour le front-end et un pour le back-end.
# Se placer dans le dossier des sites web
cd /var/www
# Créer les dossiers du projet
sudo mkdir -p frontend backend
# Donner la propriété à l'utilisateur de déploiement
sudo chown -R deploy:deploy /var/www/frontend /var/www/backendL'arborescence obtenue :
/var/www
├── frontend/ # code du site / de l'interface
└── backend/ # code de l'APILier le VPS et GitHub par une clé SSH dédiée
Objectif : donner à GitHub Actions une clé propre pour se connecter au serveur, distincte de votre clé personnelle et révocable à tout moment.
1Générer une paire de clés sur le VPS
On crée une clé ed25519 (moderne et compacte) sans passphrase, car un pipeline ne peut pas en saisir une.
ssh-keygen -t ed25519 -f ~/.ssh/github_actions -N ""Deux fichiers sont créés :
~/.ssh/github_actions # clé privée -> ira dans un secret GitHub
~/.ssh/github_actions.pub # clé publique -> reste sur le serveur2Autoriser cette clé sur le VPS
On ajoute la clé publique aux clés acceptées par le serveur, et on verrouille les permissions.
# Ajouter la clé publique aux clés autorisées
cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys
# Verrouiller les permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/github_actions3Récupérer la clé privée
On affiche la clé privée : son contenu ira dans le secret GitHub SERVER_SSH_KEY à la section suivante.
cat ~/.ssh/github_actionsRenseigner les secrets et variables GitHub
Objectif : confier au dépôt GitHub les informations dont le pipeline a besoin, en séparant ce qui est sensible (secrets) de ce qui ne l'est pas (variables).
Dans le dépôt GitHub, ouvrez Settings → Secrets and variables → Actions. L'onglet propose deux sections.
Onglet « Secrets » : chiffrés, jamais réaffichés
SERVER_IP: l'adresse IP du VPS.SERVER_USER: l'utilisateur SSH, soitdeploy.SERVER_SSH_KEY: la clé privéegithub_actionscopiée à la section V.SMTP_PASSWORD: le mot de passe du compte d'envoi d'e-mails de l'application.
Onglet « Variables » : en clair
SMTP_USERNAME: l'identifiant du compte SMTP.SMTP_TO: l'adresse de réception des e-mails.
Écrire le pipeline de déploiement continu
Objectif : à chaque git pushsur une branche dédiée, GitHub se connecte au VPS, récupère le code, installe les dépendances et redémarre l'application, sans intervention.
1Créer la branche de déploiement
Le pipeline ne se déclenchera que sur une branche précise. On crée production en local.
git checkout -b production
git push -u origin production2Créer le fichier du workflow
GitHub Actions lit les workflows depuis un emplacement précis du dépôt :
.github/
└── workflows/
└── deploy.yml3Définir le workflow de déploiement
Voici un deploy.yml complet et réutilisable. Il se connecte au serveur en SSH, met le code à jour, installe les dépendances, construit le projet et le (re)lance avec PM2. Adaptez le chemin, le nom du processus et les commandes de build à votre projet.
name: Déploiement sur le VPS
# Se déclenche à chaque push sur la branche production
on:
push:
branches: [production]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Récupérer le code
uses: actions/checkout@v4
- name: Déployer via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /var/www/backend
git pull origin production
npm ci
npm run build
pm2 reload ecosystem.config.js --update-env
pm2 save4Décrire l'application pour PM2
Le pipeline appelle ecosystem.config.js: ce fichier, à la racine du projet, décrit comment lancer l'application. On le versionne dans le dépôt.
module.exports = {
apps: [
{
name: "backend",
script: "npm",
args: "start",
cwd: "/var/www/backend",
env: {
NODE_ENV: "production",
PORT: 3000,
},
},
],
};Au tout premier déploiement, l'application n'est pas encore connue de PM2 : lancez-la une fois à la main sur le serveur, puis figez la liste.
cd /var/www/backend
pm2 start ecosystem.config.js
pm2 save
# Suivre les journaux de l'application
pm2 logs backendConfigurer Nginx en reverse proxy et activer le HTTPS
Objectif : exposer l'application au monde via le nom de domaine, et chiffrer le trafic avec un certificat SSL gratuit.
1Créer le fichier de configuration du domaine
Les configurations de sites vivent dans /etc/nginx/sites-available. On crée un fichier portant le nom du domaine.
sudo nano /etc/nginx/sites-available/api.mon-domaine.comCette configuration reçoit le trafic HTTP et le transmet à l'application Node.js, qui écoute en local sur le port 3000.
server {
listen 80;
server_name api.mon-domaine.com;
location / {
proxy_pass http://localhost: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;
}
}2Activer le site
Nginx ne sert que les configurations présentes dans sites-enabled. On y crée un lien symbolique vers notre fichier, on vérifie la syntaxe, puis on recharge.
# Activer le site
sudo ln -s /etc/nginx/sites-available/api.mon-domaine.com \
/etc/nginx/sites-enabled/
# Vérifier que la configuration est valide
sudo nginx -t
# Recharger Nginx pour appliquer
sudo systemctl reload nginx3Générer le certificat SSL avec Certbot
Certbot obtient le certificat pour le domaine et modifie automatiquement la configuration Nginx pour activer le HTTPS et rediriger le HTTP vers le HTTPS.
# Obtenir et installer le certificat
sudo certbot --nginx -d api.mon-domaine.com
# Vérifier que le renouvellement automatique fonctionne
sudo certbot renew --dry-runDépanner les erreurs les plus courantes
Trois erreurs reviennent presque à chaque déploiement. Voici comment les diagnostiquer rapidement.
502 Bad Gateway
Nginx fonctionne mais ne joint pas l'application : elle est arrêtée, a planté, ou n'écoute pas sur le port attendu. On vérifie l'état du processus et ses journaux.
# L'application tourne-t-elle ?
pm2 status
# Que disent ses journaux ?
pm2 logs backend --lines 50Permission denied (publickey)
Le serveur refuse la clé. Causes fréquentes : permissions trop larges sur ~/.ssh, mauvais utilisateur dans l'alias, ou clé absente de authorized_keys. On relance la connexion en mode bavard pour voir où elle bloque.
ssh -v mon-vpsEADDRINUSE : port déjà utilisé
Une ancienne instance occupe encore le port 3000. On identifie le processus fautif, puis on demande à PM2 de repartir proprement.
# Quel processus occupe le port 3000 ?
sudo lsof -i :3000
# Repartir d'une base saine
pm2 delete backend
pm2 start ecosystem.config.js
pm2 saveChecklist finale
Une fois toutes les sections suivies, voici ce qui doit être vrai. Si une case n'est pas cochée, reprenez la section correspondante.
ssh mon-vpsconnecte au serveur sans mot de passe, sur le comptedeploy.- La connexion
rootet par mot de passe est désactivée ; UFW est actif. - Node.js 20, Nginx, Certbot et PM2 sont installés et fonctionnels.
- Le code vit dans
/var/wwwet appartient àdeploy. - Les secrets et variables sont renseignés dans GitHub ;
deploy.ymletecosystem.config.jssont versionnés. - Un
git push origin productiondéclenche le déploiement automatiquement. - Le domaine répond en
https://avec un certificat valide.