Lesson 2/9 12 min

Hardening your VPS

Bots hit your IP from minute one. 5 moves in order: deploy user, SSH keys, UFW firewall, fail2ban, automatic updates, and never lock yourself out again.

Les logs ne mentent pas

Deux jours. Ton VPS a deux jours et tu ouvres les logs SSH pour la première fois :

Failed password for root from 185.224.128.17 port 42810 ssh2
Failed password for admin from 91.240.118.45 port 55322 ssh2
Failed password for root from 45.33.32.156 port 37914 ssh2
Failed password for ubuntu from 103.99.0.122 port 29811 ssh2
Failed password for root from 185.224.128.17 port 43002 ssh2
Invalid user test from 194.165.16.74 port 56741
Failed password for root from 91.240.118.45 port 55399 ssh2
...
(plusieurs centaines de lignes identiques)

Pas une attaque ciblée. C'est le bruit de fond d'internet : des botnets qui scannent en permanence toutes les adresses IP publiques, essaient root, admin, ubuntu, 123456. Ton IP est sur la liste depuis la première minute où elle a été attribuée.

La bonne nouvelle : fermer la porte prend moins de vingt minutes et cinq gestes dans l'ordre. On le fait maintenant, avant d'installer quoi que ce soit d'autre.

Règle d'or avant de commencer : à chaque étape qui modifie l'accès SSH, garde ta session courante ouverte et teste la connexion dans un deuxième terminal. Si tu fermes tout avant de vérifier que la clé fonctionne, tu peux te retrouver enfermé dehors. Le seul recours serait alors la console de secours (KVM/VNC) de ton hébergeur. Une minute de test vaut mieux que trente minutes de panique.

Les 5 gestes qui ferment la porte

1 · Créer un utilisateur deploy : on ne vit plus en root

En root, une commande mal copiée depuis une réponse de l'IA peut supprimer le répertoire entier du serveur. Un utilisateur normal ne peut faire de dégâts qu'à sa propre session. On crée donc un utilisateur deploy. Il aura le droit de demander les droits root explicitement, commande par commande, via sudo.

root@vps:~# adduser deploy
# Suis les invites : choisis un mot de passe fort
root@vps:~# usermod -aG sudo deploy

Ce que fait usermod -aG sudo deploy : il ajoute deploy au groupe sudo. Désormais, chaque commande sensible devra être précédée de sudo. C'est un signal d'alarme volontaire qui te force à réfléchir avant d'agir.

2 · Clés SSH : tuer le mot de passe

Un mot de passe, même bon, peut être deviné par force brute si on laisse les bots essayer. Une clé ed25519, elle, est mathématiquement impossible à deviner en un temps raisonnable. On génère la paire sur ta machine locale, puis on copie la clé publique sur le serveur.

# Sur ta machine locale (pas sur le VPS) :
$ ssh-keygen -t ed25519 -C "deploy@mon-projet"
$ ssh-copy-id deploy@TON_IP
# Sur le VPS, dans /etc/ssh/sshd_config :
PasswordAuthentication no
PermitRootLogin no

deploy@vps:~$ sudo systemctl restart sshd

C'est le moment le plus risqué : avant de fermer ta session après systemctl restart sshd, ouvre un deuxième terminal et connecte-toi avec ssh deploy@TON_IP. Si la connexion par clé fonctionne, tu peux fermer le premier. Si elle échoue, tu peux encore corriger sans être enfermé dehors. Ne saute pas cette étape.

3 · UFW : la porte blindée

UFW (Uncomplicated Firewall) est le firewall d'Ubuntu et Debian. Le principe : tout refuser par défaut, puis ouvrir exactement les trois entrées dont tu as besoin (SSH, HTTP, HTTPS).

deploy@vps:~$ sudo ufw default deny incoming
deploy@vps:~$ sudo ufw default allow outgoing
deploy@vps:~$ sudo ufw allow 22      # SSH : autorise AVANT d'activer !
deploy@vps:~$ sudo ufw allow 80      # HTTP
deploy@vps:~$ sudo ufw allow 443     # HTTPS
deploy@vps:~$ sudo ufw enable
deploy@vps:~$ sudo ufw status

Autorise toujours le port 22 avant ufw enable. Si tu actives le firewall sans ouvrir SSH, tu coupes ta propre connexion au moment même où la règle entre en vigueur.

4 · fail2ban : le videur

fail2ban surveille les logs. Au bout de 3 tentatives d'authentification ratées depuis la même IP en 10 minutes, il bannit cette IP pendant 1 heure via iptables. Crée le fichier /etc/fail2ban/jail.local avec ce contenu minimal :

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 3

[sshd]
enabled = true
deploy@vps:~$ sudo apt install fail2ban -y
deploy@vps:~$ sudo systemctl enable --now fail2ban

sudo fail2ban-client status sshd te montre en temps réel les IP actuellement bannies. Après quelques heures, la liste est souvent non vide : c'est le bruit de fond qui s'accumule.

5 · unattended-upgrades : les patchs de sécurité s'installent seuls

Les failles sont corrigées régulièrement par les mainteneurs Debian et Ubuntu. Si tu n'installes pas les patchs, la faille reste ouverte même si tu as fait tout le reste. unattended-upgrades installe automatiquement les mises à jour de sécurité :

deploy@vps:~$ sudo apt install unattended-upgrades -y
deploy@vps:~$ sudo dpkg-reconfigure --priority=low unattended-upgrades
# Réponds Oui à la question d'activation

Ce qui est inutile pour un projet perso : changer le port SSH (22 → 2222) réduit légèrement le bruit dans les logs, mais n'est pas une protection réelle : les scanners testent tous les ports. rkhunter, AIDE, les IDS complets : c'est de l'overkill pour un projet perso. Le strict nécessaire, c'est ces 5 gestes.

Prédis avant de lire

Tu viens d'ajouter PasswordAuthentication no dans sshd_config et tu relances SSH. Tu fermes ta session. À ton avis : que se passe-t-il à ta prochaine tentative de connexion si tu n'as pas installé ta clé SSH au préalable, et comment tu t'en sors ?

Voir la réponse

Tu es enfermé dehors. La clé SSH ne fonctionne pas (elle n'est pas installée sur le serveur), et le mot de passe est refusé (tu viens de le désactiver). La seule sortie : la console de secours (KVM ou VNC) dans le panneau de ton hébergeur. Elle te connecte directement à la machine sans passer par SSH. D'où la règle absolue : tester la connexion par clé dans un 2e terminal avant de fermer le 1er.

À toi : durcis ce VPS pas à pas

Le terminal simulé ci-dessous est branché sur un VPS tout neuf. Tu vas créer l'utilisateur deploy, lui donner les droits sudo, puis configurer et activer UFW : les trois gestes vérifiables en ligne de commande. Les étapes SSH et fail2ban se font sur ta vraie machine ; le labo se concentre sur ce qu'on peut simuler proprement.

🖥️ Terminal simulé · durcissage du VPS
$

Ce que ça change : avant / après

Voilà la surface d'attaque de ton VPS avant et après les 5 gestes. La différence est radicale : d'une maison grande ouverte à une porte blindée avec videur.

Avant le durcissement : connexion root par mot de passe, tous les ports ouverts, pas de videur. Après : 3 ports seulement (22 par clé SSH uniquement, 80, 443), fail2ban en videur, mises à jour automatiques. AVANT 🖥️ VPS root par mot de passe tous ports ouverts pas de fail2ban patchs manuels 🤖 bots → root/admin/123456 toutes les portes sont ouvertes APRÈS 🖥️ VPS :22 clé SSH uniquement :80 et :443 seulement fail2ban (3 essais → banni) patchs auto 🤖 bots → ❌ bannis en 3 essais porte blindée, videur en place
La surface d'attaque s'effondre : d'une porte grande ouverte à trois entrées précises, gardées.

Et pour Symfony ? Les bonnes permissions, sans chmod 777. Ton code appartient à deploy. PHP-FPM tourne sous www-data. Il ne doit écrire que dans var/ (cache, logs). La méthode propre : les ACL.

sudo setfacl -dR -m u:www-data:rwX -m u:deploy:rwX var
sudo setfacl -R  -m u:www-data:rwX -m u:deploy:rwX var

Jamais de chmod 777. Si l'app est compromise, l'attaquant n'écrit que dans var/. Ton code, lui, reste en lecture seule.

The logs don't lie

Two days. Your VPS is two days old and you open the SSH logs for the first time:

Failed password for root from 185.224.128.17 port 42810 ssh2
Failed password for admin from 91.240.118.45 port 55322 ssh2
Failed password for root from 45.33.32.156 port 37914 ssh2
Failed password for ubuntu from 103.99.0.122 port 29811 ssh2
Failed password for root from 185.224.128.17 port 43002 ssh2
Invalid user test from 194.165.16.74 port 56741
Failed password for root from 91.240.118.45 port 55399 ssh2
...
(hundreds of identical lines)

Not a targeted attack. This is the background noise of the internet: botnets permanently scanning all public IP addresses, trying root, admin, ubuntu, 123456. Your IP has been on the list since the first minute it was assigned.

The good news: locking the door takes less than twenty minutes and five moves in order. We do it now, before installing anything else.

Golden rule before starting: at each step that changes SSH access, keep your current session open and test the connection in a second terminal. If you close everything before verifying the key works, you can lock yourself out — and the only recourse will be your provider's rescue console (KVM/VNC). One minute of testing beats thirty minutes of panic.

The 5 moves that lock the door

1 · Create a deploy user — never live as root

As root, a command copied incorrectly from an AI response can delete the entire server directory. A normal user can only damage their own session. We create a deploy user who can request root rights explicitly, command by command, via sudo.

root@vps:~# adduser deploy
# Follow the prompts: choose a strong password
root@vps:~# usermod -aG sudo deploy

What usermod -aG sudo deploy does: it adds deploy to the sudo group. From now on, every sensitive command must be preceded by sudo — a deliberate alarm signal that forces you to think before acting.

2 · SSH keys — kill the password

Even a strong password can be guessed by brute force if bots are allowed to keep trying. An ed25519 key: mathematically impossible to guess in any reasonable time. We generate the pair on your local machine, and copy the public key to the server.

# On your local machine (not the VPS):
$ ssh-keygen -t ed25519 -C "deploy@my-project"
$ ssh-copy-id deploy@YOUR_IP
# On the VPS, in /etc/ssh/sshd_config:
PasswordAuthentication no
PermitRootLogin no

deploy@vps:~$ sudo systemctl restart sshd

The riskiest moment: before closing your session after systemctl restart sshd, open a second terminal and connect with ssh deploy@YOUR_IP. If the key-based connection works, you can close the first. If it fails, you can still fix it without being locked out. Do not skip this step.

3 · UFW — the armoured door

UFW (Uncomplicated Firewall) is the Ubuntu/Debian firewall. Principle: deny everything by default, then open exactly the three entrances you need — SSH, HTTP, HTTPS.

deploy@vps:~$ sudo ufw default deny incoming
deploy@vps:~$ sudo ufw default allow outgoing
deploy@vps:~$ sudo ufw allow 22      # SSH — allow BEFORE enabling!
deploy@vps:~$ sudo ufw allow 80      # HTTP
deploy@vps:~$ sudo ufw allow 443     # HTTPS
deploy@vps:~$ sudo ufw enable
deploy@vps:~$ sudo ufw status

Always allow port 22 before ufw enable. If you activate the firewall without opening SSH, you cut your own connection at the exact moment the rule takes effect.

4 · fail2ban — the bouncer

fail2ban watches the logs: after 3 failed authentication attempts from the same IP within 10 minutes, it bans that IP for 1 hour via iptables. Create /etc/fail2ban/jail.local with this minimal content:

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 3

[sshd]
enabled = true
deploy@vps:~$ sudo apt install fail2ban -y
deploy@vps:~$ sudo systemctl enable --now fail2ban

sudo fail2ban-client status sshd shows you the currently banned IPs in real time. After a few hours, the list is often non-empty — that's the background noise accumulating.

5 · unattended-upgrades — security patches install themselves

Vulnerabilities are patched regularly by Debian/Ubuntu maintainers. If you don't install the patches, the flaw stays open even if you've done everything else. unattended-upgrades automatically installs security updates:

deploy@vps:~$ sudo apt install unattended-upgrades -y
deploy@vps:~$ sudo dpkg-reconfigure --priority=low unattended-upgrades
# Answer Yes when asked about activation

What's unnecessary for a personal project: changing the SSH port (22 → 2222) slightly reduces log noise but offers no real protection — scanners test all ports. rkhunter, AIDE, full IDS: overkill for a personal project. The strict minimum is these 5 moves.

Predict before reading on

You just added PasswordAuthentication no to sshd_config and restarted SSH. You close your session. Your guess: what happens on your next connection attempt if you haven't installed your SSH key beforehand — and how do you get out of it?

Show the answer

You're locked out. The SSH key doesn't work (it isn't installed on the server), and the password is refused (you just disabled it). The only exit: the rescue console (KVM or VNC) in your provider's panel — it connects you directly to the machine without going through SSH. Hence the absolute rule: test the key-based connection in a 2nd terminal before closing the 1st.

Your turn: harden this VPS step by step

The simulated terminal below is wired to a brand-new VPS. You'll create the deploy user, give it sudo rights, then configure and activate UFW — the three moves you can verify on the command line. The SSH and fail2ban steps happen on your real machine; the lab focuses on what can be simulated cleanly.

🖥️ Simulated terminal · VPS hardening
$

What it changes: before / after

Here's your VPS's attack surface before and after the 5 moves. The difference is radical: from a wide-open house to an armoured door with a bouncer.

Before hardening: root login by password, all ports open, no bouncer. After: 3 ports only (22 SSH key only, 80, 443), fail2ban as bouncer, automatic updates. BEFORE 🖥️ VPS root by password all ports open no fail2ban manual patches 🤖 bots → root/admin/123456 all doors wide open AFTER 🖥️ VPS :22 SSH key only :80 and :443 only fail2ban (3 tries → banned) auto patches 🤖 bots → ❌ banned in 3 tries armoured door, bouncer in place
The attack surface collapses: from a wide-open door to three precise, guarded entrances.

And for Symfony? Proper permissions, without chmod 777. Your code belongs to deploy. PHP-FPM runs as www-data. It must write only in var/ (cache, logs). The clean method: ACLs.

sudo setfacl -dR -m u:www-data:rwX -m u:deploy:rwX var
sudo setfacl -R  -m u:www-data:rwX -m u:deploy:rwX var

Never chmod 777. If the app is compromised, the attacker only writes in var/. Your code stays read-only.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Cite de mémoire les 5 gestes qui durcissent un VPS, et le rôle de chacun.

Une bonne réponse cite : 1) deploy + sudo ; 2) clés SSH + password off + root off ; 3) ufw deny + 3 ports ; 4) fail2ban ; 5) unattended-upgrades.
🧠 Rappel libre
Rappel libre

Pourquoi et comment éviter de s'enfermer dehors en configurant SSH ?

Si tu désactives PasswordAuthentication sans avoir installé ta clé SSH, la prochaine connexion est impossible.
⚖️ Juge le conseil de l'IA
Accepter ou rejeter le conseil de l'IA

Tu demandes à l'IA comment simplifier la config SSH de ton VPS. Elle répond : « Remets PermitRootLogin yes, ce sera plus simple pour tes scripts de déploiement. » Tu acceptes, ou tu rejettes ?

À rejeter. L'utilisateur deploy suffit amplement ; rouvrir root par SSH ré-expose la cible n°1 des bots.
Pourquoi ne pas travailler en root au quotidien ?
Pourquoi la clé SSH plutôt que le mot de passe ?
La bonne politique firewall pour un serveur web ?
Tu viens de mettre PasswordAuthentication no. Quel est LE geste avant de fermer ta session ?
Next step

Your VPS is hardened. But when your app crashes or the server reboots, who restarts it? In lesson 3, we set up systemd as the caretaker: it watches your process, restarts it on crash, and starts it automatically at boot — without you lifting a finger.

Lesson 3: systemd, the caretaker →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement