How-to — hardening sécurité d'un VPS LAMP Symfony
Documente les opérations manuelles appliquées au VPS OVH le 2026-06-17. Sera ultérieurement remplacé ou complété par des rôles Ansible (
tlr-ansible). Le gap poseidon/prod est assumé.
Prérequis : VPS Ubuntu fraîchement provisionné, accès root ou sudo complet.
Pour l'état cible attendu après application : référence sécurité VPS.
Étape 1 — Créer adminvps avant de toucher ubuntu
# En tant que ubuntu (avant toute restriction) adduser adminvps usermod -aG sudo adminvps mkdir -p /home/adminvps/.ssh cp ~/.ssh/authorized_keys /home/adminvps/.ssh/ chown -R adminvps:adminvps /home/adminvps/.ssh chmod 700 /home/adminvps/.ssh && chmod 600 /home/adminvps/.ssh/authorized_keys
Ouvrir une deuxième session SSH avec adminvps et vérifier sudo -i avant de continuer.
Étape 2 — Restreindre sudo ubuntu
# /etc/sudoers.d/ubuntu ubuntu ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/systemctl, /usr/sbin/ufw, /usr/bin/journalctl # /etc/sudoers.d/adminvps adminvps ALL=(ALL) ALL
Attention : si
cloud-inita laissé/etc/sudoers.d/90-cloud-init-usersavecubuntu ALL=(ALL) NOPASSWD:ALL, commenter cette ligne (viaadminvps+sudo -S) — sinon la restriction ne prend pas effet.
Étape 3 — Durcir SSH
# /etc/ssh/sshd_config.d/70-telaria-hardening.conf AllowUsers ubuntu adminvps PermitRootLogin no
sudo systemctl reload ssh
PasswordAuthentication no est déjà dans /etc/ssh/sshd_config.d/60-cloudimg-settings.conf (OVH).
Étape 4 — fail2ban
sudo apt install fail2ban
# /etc/fail2ban/jail.d/sshd-telaria.conf [sshd] enabled = true port = 9501 filter = sshd maxretry = 3 bantime = 1h findtime = 10m
sudo systemctl enable fail2ban && sudo systemctl restart fail2ban
Étape 5 — UFW sortant verrouillé
sudo ufw default deny incoming sudo ufw default deny outgoing sudo ufw allow in 80/tcp sudo ufw allow in 443/tcp sudo ufw allow in 9501/tcp sudo ufw allow out 443/tcp sudo ufw allow out 587/tcp sudo ufw allow out 53 sudo ufw allow out 123/udp sudo ufw enable
Étape 6 — MySQL moindre privilège
-- Vérifier les droits du user applicatif SHOW GRANTS FOR 'telariauser'@'localhost'; -- Attendu : SELECT, INSERT, UPDATE, DELETE sur telaria_dev.* et telaria_fr.* -- Aucun GRANT ALL, aucun droit global
Supprimer tout user superflu. Ne conserver que telariauser@localhost.
Étape 7 — Permissions /var/www
# Propriété et droits de base pour chaque app for app in telaria telaria-fr adoption-ia-fr vps-ovh-net; do chown -R telaria:www-data /var/www/$app find /var/www/$app -type d -exec chmod 750 {} \; find /var/www/$app -type f -exec chmod 640 {} \; done # var/ — www-data écrit (cache/log Symfony) for app in telaria telaria-fr adoption-ia-fr; do chown -R www-data:www-data /var/www/$app/var chmod -R 775 /var/www/$app/var done # .env.local — secrets for app in telaria telaria-fr adoption-ia-fr; do chown telaria:www-data /var/www/$app/.env.local chmod 640 /var/www/$app/.env.local done
Avertissement : ne jamais appliquer les chmod récursivement sur un site actif sans exclure
var/de la passe fichiers. Un chmod 640 sur les fichiers de cache Symfony casse le site immédiatement.
Étape 8 — Apache
sudo a2dismod autoindex status sudo systemctl reload apache2
Dans /etc/apache2/conf-available/security.conf :
ServerTokens Prod ServerSignature Off TraceEnable Off
Dans chaque <VirtualHost> :
Options -Indexes -FollowSymLinks <FilesMatch "\.(env|log|git|htpasswd)"> Require all denied </FilesMatch>
Vhost catch-all 000-default (chargé en premier par le préfixe 000-) :
<VirtualHost *:80> <Location "/"> Require all denied </Location> </VirtualHost>
Étape 9 — PHP FPM
# /etc/php/8.5/fpm/conf.d/99-telaria-security.ini [PHP] open_basedir = /var/www:/tmp:/var/lib/php/sessions disable_functions = exec,passthru,shell_exec
sudo systemctl reload php8.5-fpm # Vérifier absence de xdebug en FPM php-fpm8.5 -m | grep xdebug # doit retourner vide
Étape 10 — unattended-upgrades
sudo apt install unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades
Note Ansible
Ces opérations seront à terme gérées par tlr-ansible. Quand un rôle Ansible couvrira une étape, la section sera annotée [→ voir rôle ansible X] plutôt que supprimée.