Terminal et Linux
Se sentir chez soi sur un serveur : trouver, lire, comprendre, et ne rien casser.
Tôt ou tard, il faut se connecter à un serveur : lire un log pendant une panne, vérifier qu'un service tourne, comprendre pourquoi Symfony ne peut pas écrire dans var/. Sur un serveur, il n'y a pas d'interface graphique : seulement un terminal.
Bonne nouvelle : une trentaine de commandes couvrent l'essentiel du quotidien. Cette fiche les présente par besoin, avec les pièges qui coûtent cher. Personne ne retient toutes les options par cœur : tout le monde passe son temps dans man et --help.
Toutes les commandes de cette fiche sont des illustrations : aucune n'a été exécutée pour la préparer. Les adresses IP sont fictives, et les noms de services supposent un serveur Debian ou Ubuntu avec PHP 8.4. Essaie-les d'abord sur ta machine ou sur un serveur de test, jamais directement en production.
1.Le terminal et le shell
Le terminal est la fenêtre. Le shell est le programme qui lit ce que tu tapes et lance les commandes : Bash sur la plupart des serveurs, Zsh sur beaucoup de postes de développement. Une commande suit toujours la même forme :
# Une commande : le programme, des options (souvent avec - ou --), des arguments
ls -l --human-readable var/log
# │ │ │ └─ argument : sur quoi agir
# │ │ └─ option longue
# │ └─ option courte
# └─ programme
man ls # le manuel complet (q pour quitter, / pour chercher)
ls --help # l'aide courteTrois réflexes font gagner un temps énorme :
- La touche Tab complète les noms de commandes et de fichiers. Deux appuis affichent les possibilités.
- Ctrl+R cherche dans l'historique des commandes déjà tapées.
- Ctrl+C interrompt la commande en cours.
2.Fichiers et dossiers
Sous Linux, tout part de la racine /. Un chemin qui commence par / est absolu (/var/www/shop), les autres sont relatifs au dossier courant. ~ désigne ton dossier personnel, . le dossier courant, .. le dossier parent.
pwd # où suis-je ? (print working directory)
ls -la # le contenu du dossier, fichiers cachés compris
cd /var/www/shop # aller dans un dossier (chemin absolu, depuis la racine)
cd .. # remonter d'un niveau
cd - # revenir au dossier précédent
mkdir -p var/exports/2026 # créer un dossier, et ses parents si besoin
cp .env .env.backup # copier
mv export.csv var/exports/ # déplacer, ou renommer
rm var/exports/old.csv # supprimer : PAS de corbeille, c'est définitif
less var/log/prod.log # lire un fichier page par page (q pour quitter)
head -n 20 composer.json # les 20 premières lignes
tail -n 50 var/log/prod.log # les 50 dernières
find src -name '*Controller.php' # chercher des fichiers par leur nom
grep -rn 'OrderPlaced' src/ # chercher un texte, avec le n° de ligneSur un serveur web, quelques dossiers reviennent toujours : /var/www (les sites), /var/log (les logs du système et de nginx), /etc (la configuration : /etc/nginx, /etc/php/8.4), /tmp (les fichiers temporaires, vidés au redémarrage).
rm supprime définitivement : il n'y a pas de corbeille. rm -rf supprime un dossier entier sans rien demander, et une variable vide peut transformer rm -rf "$DIR/" en rm -rf /. Avant un rm avec un motif (*), lance d'abord ls avec le même motif pour voir ce qui serait supprimé.
3.Tubes et redirections
La force du terminal, c'est de combiner de petits outils. Chaque commande lit une entrée et produit deux sorties : la sortie normale (numéro 1) et la sortie d'erreur (numéro 2). On peut les rediriger vers un fichier, ou les brancher sur une autre commande avec un tube (|) :
# Redirections : où va la sortie d'une commande
php bin/console app:export > export.csv # écrit dans un fichier (l'écrase)
php bin/console app:export >> export.csv # ajoute à la fin du fichier
php bin/console app:import 2> errors.log # seulement les erreurs (sortie n° 2)
php bin/console app:import > all.log 2>&1 # sortie normale ET erreurs dans le même fichier
# Tube (|) : la sortie d'une commande devient l'entrée de la suivante
grep 'CRITICAL' var/log/prod.log | tail -n 20
# Les 10 adresses IP qui font le plus de requêtes, dans le log d'accès de nginx
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 10
# awk : garde la 1re colonne (l'IP)
# sort : trie, pour regrouper les IP identiques
# uniq -c : compte chaque groupe
# sort -rn: trie par nombre, du plus grand au plus petit
# head : garde les 10 premières lignesLa dernière commande répond en une ligne à une vraie question de production : « qui bombarde le serveur ? ». Chaque outil ne fait qu'une chose, et le tube les enchaîne. C'est la même idée que les petites fonctions de la fiche 2.
Chaque commande se termine aussi par un code de sortie, que les scripts utilisent pour enchaîner les étapes :
# Chaque commande se termine par un code : 0 = succès, autre chose = échec
php bin/phpunit
echo $? # affiche le code de la dernière commande
# && : la suite ne s'exécute QUE si la commande précédente a réussi
composer install --no-dev && php bin/console cache:clear
# || : la suite ne s'exécute que si la commande précédente a échoué
php bin/console doctrine:migrations:migrate --no-interaction || echo 'Migration échouée'C'est ce code que lit git bisect run (fiche 4), et ce code que regarde l'intégration continue pour savoir si les tests ont réussi.
4.Processus et services
Un processus est un programme en cours d'exécution, avec un numéro (le PID). Un service est un processus qui tourne en permanence en arrière-plan : nginx, PHP-FPM, PostgreSQL. Sur la plupart des distributions, les services sont gérés par systemd, avec la commande systemctl.
ps aux | grep php-fpm # les processus en cours, filtrés
top # l'activité en direct : CPU, mémoire (q pour quitter)
kill 12345 # demande poliment au processus 12345 de s'arrêter
kill -9 12345 # le tue sur-le-champ : en dernier recours
# Un service (nginx, PHP-FPM, PostgreSQL) est géré par systemd
systemctl status php8.4-fpm # est-il démarré ? depuis quand ? dernières lignes de log
sudo systemctl restart nginx # le redémarrer
sudo systemctl reload nginx # relire sa configuration, sans couper les connexions
sudo nginx -t # vérifier la configuration de nginx AVANT de rechargerreloadplutôt querestartquand c'est possible : le service relit sa configuration sans couper les requêtes en cours.- Vérifie avant de recharger :
nginx -tteste la configuration. Une faute de frappe dans un fichier de configuration, rechargée sans test, peut arrêter le site. kill -9en dernier recours : le processus est tué sur-le-champ, sans finir d'écrire ses fichiers ni fermer ses connexions.
5.Utilisateurs et permissions
Linux est multi-utilisateur. Chaque fichier appartient à un propriétaire et à un groupe, et a trois séries de droits : pour le propriétaire, pour le groupe, et pour les autres.
$ ls -l
-rw-r----- 1 deploy www-data 1820 nov. 3 10:12 .env.local
drwxrwxr-x 5 deploy www-data 4096 nov. 3 10:12 var
-rw-r----- type et permissions
│└┬┘└┬┘└┬┘
│ │ │ └─ les autres : --- aucun droit
│ │ └──── le groupe www-data : r-- lire
│ └─────── le propriétaire : rw- lire et écrire
└───────── - fichier, d dossier
r = lire (4) w = écrire (2) x = exécuter, ou entrer dans un dossier (1)
rw-r----- = 6 4 0 → chmod 640
chmod 640 .env.local # propriétaire : rw ; groupe : r ; autres : rien
chmod u+x bin/deploy.sh # ajouter le droit d'exécution au propriétaire
sudo chown -R deploy:www-data var/ # changer le propriétaire et le groupe, récursivement
whoami # quel utilisateur suis-je ?
id # et dans quels groupes ?
sudo -u www-data php bin/console cache:clear # lancer une commande en tant que www-dataSur un serveur web, PHP-FPM tourne en général sous l'utilisateur www-data. C'est lui qui doit pouvoir écrire dans var/ (le cache et les logs de Symfony), et seulement lire le code. Le code lui-même appartient à un utilisateur de déploiement (ici deploy).
chmod 777 donne tous les droits à tout le monde. C'est la « solution » qu'on trouve partout quand Symfony n'arrive pas à écrire dans var/, et c'est une faille : n'importe quel processus du serveur peut modifier ton code. Donne les droits au bon utilisateur ou au bon groupe, et seulement là où il en a besoin. Le même principe vaut pour sudo : ne l'utilise que pour la commande qui en a besoin, et ne déploie jamais en root.
6.Variables d'environnement
Une variable d'environnement est une valeur nommée que chaque processus hérite de celui qui l'a lancé. C'est par elles que Symfony reçoit APP_ENV, DATABASE_URL ou ses secrets en production (fiche 10).
echo $HOME # afficher une variable
printenv | grep APP_ # lister les variables qui commencent par APP_
export APP_ENV=prod # définir une variable pour ce terminal et ses enfants
APP_ENV=test php bin/phpunit # la définir pour UNE seule commande
echo $PATH # les dossiers où le shell cherche les programmes
# Les réglages permanents de ton terminal se mettent dans ~/.bashrc (ou ~/.zshrc)Une commande tapée avec un mot de passe en clair (export DATABASE_PASSWORD=…, mysql -p…) est enregistrée dans ~/.bash_history. Évite de taper un secret directement dans une commande : préfère un fichier protégé (chmod 600), ou une saisie interactive. Sur beaucoup de systèmes, une commande précédée d'une espace n'est pas enregistrée (réglage HISTCONTROL=ignorespace), mais vérifie avant de compter dessus.
7.Lire les logs
Pendant une panne, les logs sont la première source d'information. Il en existe trois sortes sur un serveur Symfony : ceux de l'application (var/log/prod.log, ou la sortie d'erreur selon la configuration), ceux de nginx (/var/log/nginx/), et ceux des services gérés par systemd.
tail -f var/log/prod.log # suivre en direct (Ctrl+C pour arrêter)
tail -f var/log/prod.log | grep --line-buffered ERROR # en direct, seulement les erreurs
journalctl -u nginx --since '1 hour ago' # les logs d'un service systemd, depuis une heure
journalctl -u php8.4-fpm -f # en direct
less +G /var/log/nginx/error.log # ouvrir directement à la fin du fichier- Commence par l'heure : quand le problème a-t-il commencé ? Cherche les premières erreurs autour de ce moment, pas les dernières.
- Remonte la chaîne : une erreur 502 dans le log de nginx renvoie au log de PHP-FPM, qui renvoie au log de Symfony.
- Garde
tail -fouvert dans un terminal pendant que tu reproduis le problème dans le navigateur.
La fiche 13 revient sur ce qu'il faut écrire dans les logs, et sur leur centralisation.
8.SSH : se connecter à un serveur
SSH ouvre un terminal sur une machine distante, à travers une connexion chiffrée. On s'authentifie avec une paire de clés plutôt qu'avec un mot de passe :
- La clé privée reste sur ta machine. C'est elle qui prouve ton identité : elle ne se partage jamais, ne s'envoie jamais par e-mail, et ne se commite jamais.
- La clé publique se dépose sur chaque serveur, dans
~/.ssh/authorized_keys. Elle peut être vue par tout le monde sans danger.
# 1. Sur TA machine : créer une paire de clés (protège-la par une phrase de passe)
ssh-keygen -t ed25519 -C "prenom@poste-portable"
# ~/.ssh/id_ed25519 la clé PRIVÉE : ne quitte jamais ta machine
# ~/.ssh/id_ed25519.pub la clé PUBLIQUE : celle qu'on dépose sur les serveurs
# 2. Déposer la clé publique sur le serveur
ssh-copy-id deploy@203.0.113.10
# 3. Se connecter, puis copier des fichiers
ssh deploy@203.0.113.10
scp backup.sql deploy@203.0.113.10:/tmp/
rsync -avz --delete public/build/ deploy@203.0.113.10:/var/www/shop/public/build/Un fichier de configuration évite de retaper l'adresse et l'utilisateur :
# ~/.ssh/config sur TA machine : un alias par serveur
Host shop-prod
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_ed25519
# Ensuite, il suffit de taper : ssh shop-prodCôté serveur, une fois les clés en place, on désactive les connexions par mot de passe et la connexion directe en root :
# /etc/ssh/sshd_config sur le SERVEUR (extrait)
PermitRootLogin no # pas de connexion directe en root
PasswordAuthentication no # clés uniquement : plus de mot de passe à deviner
KbdInteractiveAuthentication no- Garde une session SSH ouverte pendant que tu modifies
sshd_config, et teste la nouvelle configuration dans un second terminal avant de fermer le premier. Une erreur, et plus personne ne peut se connecter. - Protège ta clé privée par une phrase de passe, et utilise l'agent SSH (
ssh-agent) pour ne pas la retaper à chaque connexion. - Une clé par personne et par poste : quand quelqu'un quitte l'équipe ou perd son ordinateur, on retire sa clé sans toucher aux autres.
9.Le pare-feu
Un serveur ne devrait accepter des connexions que sur les ports dont il a besoin : 22 pour SSH, 80 et 443 pour le web. Tout le reste (la base de données, Redis, les outils d'administration) doit rester inaccessible depuis Internet. Sur Ubuntu, ufw simplifie la configuration du pare-feu :
# L'ORDRE COMPTE : autorise SSH AVANT d'activer le pare-feu, sinon tu te bloques dehors
sudo ufw default deny incoming # tout refuser en entrée par défaut
sudo ufw default allow outgoing # autoriser le serveur à sortir (mises à jour, API)
sudo ufw allow OpenSSH # port 22
sudo ufw allow 80/tcp # HTTP (pour rediriger vers HTTPS)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable # activer
sudo ufw status verbose # vérifier- Activer le pare-feu avant d'autoriser SSH coupe ta propre connexion. Sur un serveur distant, il faut alors passer par la console de secours de l'hébergeur.
- Docker contourne
ufw: un port publié par un conteneur (ports: "5432:5432") est ouvert sur Internet, même siufwdit le contraire, car Docker modifie directement les règles réseau du système. La fiche 12 montre comment l'éviter.
Un outil comme fail2ban bloque temporairement les adresses qui échouent plusieurs fois à se connecter en SSH. Mais avec des connexions par clé uniquement, le risque principal disparaît déjà : fail2ban réduit surtout le bruit dans les logs.
Pièges fréquents
| Piège | Ce qui arrive | La parade |
|---|---|---|
rm -rf avec une variable vide ou un motif mal écrit | Des fichiers supprimés pour de bon | ls avec le même motif d'abord |
> au lieu de >> | Le fichier est écrasé au lieu d'être complété | Relire avant d'appuyer sur Entrée |
chmod 777 pour « réparer » var/ | N'importe quel processus modifie le code | Les droits au bon utilisateur ou au bon groupe |
| Déployer en root | Une faille donne tous les droits sur le serveur | Un utilisateur de déploiement dédié |
| Recharger nginx sans tester | Le site tombe à cause d'une faute de frappe | nginx -t avant reload |
kill -9 par réflexe | Fichiers à moitié écrits, connexions coupées | kill simple d'abord |
| Un mot de passe tapé dans une commande | Il reste dans l'historique | Un fichier protégé, ou une saisie interactive |
| Une clé privée partagée ou sans phrase de passe | Un vol de fichier suffit pour entrer | Une clé par personne, protégée |
Modifier sshd_config avec une seule session ouverte | Plus personne ne peut se connecter | Tester dans un second terminal |
Croire qu'ufw protège les ports Docker | La base de données est exposée sur Internet | Publier les ports sur 127.0.0.1 (fiche 12) |
Checklist sur un serveur
- Je sais sur quel serveur et dans quel dossier je suis (
hostname,pwd) avant de lancer une commande qui modifie quelque chose. - Les connexions SSH se font par clé uniquement, sans root direct.
- Le pare-feu n'ouvre que les ports 22, 80 et 443.
- Le code appartient à un utilisateur de déploiement, et
www-datan'écrit que dansvar/. - Aucun fichier contenant un secret n'est lisible par « les autres ».
- Je sais où trouver les logs de nginx, de PHP-FPM et de Symfony.
- J'ai testé la configuration avant de recharger un service.
Conseils
- Pratique sur ta machine. Un terminal sous Linux, macOS, ou WSL sous Windows suffit. Lire tes propres logs de développement avec
tail,grepet des tubes est un bon entraînement, sans aucun risque. - Lis la commande avant d'appuyer sur Entrée, surtout en production, et surtout si elle vient d'un copier-coller.
- Ne lance jamais un script téléchargé sans le lire, en particulier les fameux
curl … | bash: ils exécutent avec tes droits un code que tu n'as pas vu. - Note ce que tu fais en production, dans un ticket ou un document partagé. En cas de problème, l'équipe saura ce qui a changé.
- Utilise
mansans complexe. Les développeurs expérimentés le consultent tous les jours.