Chapitre 220 minIntermédiaire
Installer et configurer Nginx sur Debian 13
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- installer Nginx et lire son fichier de configuration ;
- créer et activer un site avec un server block ;
- prévoir quel server block et quel bloc location traitent une requête ;
- restreindre l'accès par adresse ou par mot de passe ;
- lire et exploiter les journaux d'accès et d'erreurs.
3 exercices corrigés1 schéma
Nginx (prononcé « engine-x ») est un serveur web : il reçoit les requêtes HTTP et HTTPS des navigateurs et leur renvoie des pages, des images ou la réponse d'une application. Il sert aussi de reverse proxy et de répartiteur de charge devant d'autres serveurs. Ce chapitre l'installe sur Debian 13, explique son fichier de configuration, crée un site avec un server block, puis montre comment Nginx choisit le bon site et le bon bloc location, comment restreindre un accès et comment lire ses journaux.
Installer Nginx
Sur srv-web, le serveur de la DMZ qui publie le site de suivi des livraisons :
sudo apt update
sudo apt install nginx
systemctl status nginx --no-pager
nginx -v
# nginx version: nginx/1.26.3
Le paquet démarre le service et l'active au démarrage. Vérifie qu'il répond :
curl -I http://localhost
# HTTP/1.1 200 OK
# Server: nginx
# Content-Type: text/html
L'en-tête Server: nginx n'affiche pas le numéro de version : la configuration de Debian 13 contient déjà server_tokens off. C'est une bonne pratique, qui évite d'annoncer à un attaquant la version exacte à viser. La page affichée est /var/www/html/index.nginx-debian.html, celle du site par défaut.
Nginx fonctionne avec un processus maître, lancé en root, qui lit la configuration et ouvre les ports 80 et 443, et des processus de travail (workers), sous le compte www-data, qui traitent les requêtes. Chaque processus de travail gère des milliers de connexions à la fois, sans créer un processus ni un fil d'exécution par visiteur : c'est ce modèle « événementiel » qui rend Nginx économe en mémoire sous forte charge.
ps -o user,pid,cmd -C nginx
# USER PID CMD
# root 812 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
# www-data 813 nginx: worker process
# www-data 814 nginx: worker process
L'arborescence de Nginx sous Debian
| Chemin | Rôle |
|---|---|
/etc/nginx/nginx.conf | Configuration principale : processus, journaux, options HTTP communes à tous les sites |
/etc/nginx/sites-available/ | Un fichier par site, actif ou non |
/etc/nginx/sites-enabled/ | Des liens symboliques vers les sites actifs : Nginx ne lit que ceux-là |
/etc/nginx/conf.d/ | Fragments de configuration lus pour tous les sites (*.conf) |
/etc/nginx/snippets/ | Morceaux réutilisables, inclus avec include (fastcgi-php.conf pour PHP) |
/etc/nginx/proxy_params | Les en-têtes à transmettre quand Nginx sert de reverse proxy (chapitre 10) |
/var/www/ | Racines des sites, par convention |
/var/log/nginx/ | Journaux : access.log et error.log, plus ceux que tu déclares par site |
Le couple sites-available / sites-enabled est une convention de Debian et d'Ubuntu, pas de Nginx lui-même : sur d'autres distributions, ou avec les paquets publiés par nginx.org, tout passe par conf.d/. L'intérêt est de pouvoir désactiver un site sans supprimer son fichier, en retirant simplement le lien.
Lire nginx.conf
La configuration de Nginx est faite de directives, terminées par un point-virgule, rangées dans des blocs entre accolades, appelés contextes. Voici le fichier de Debian 13 sans ses commentaires :
user www-data;
worker_processes auto;
worker_cpu_affinity auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
access_log /var/log/nginx/access.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
| Contexte | Ce qu'il règle |
|---|---|
| Principal (hors de tout bloc) | Le compte des processus de travail (user), leur nombre (worker_processes auto : un par cœur), le fichier du PID, le journal d'erreurs global |
events | Le nombre maximal de connexions simultanées par processus de travail |
http | Tout ce qui concerne le web : types MIME, compression, TLS, journaux, et l'inclusion des sites |
server | Un site (on dit server block, l'équivalent d'un VirtualHost d'Apache), dans un fichier de sites-available |
location | Le traitement d'une partie des adresses d'un site (/, /api/, *.php) |
Une directive écrite dans un contexte s'applique aux contextes qu'il contient, sauf s'ils la redéfinissent. server_tokens off dans http vaut donc pour tous les sites. Tu ne modifies presque jamais nginx.conf : les réglages d'un site vont dans son fichier, les réglages communs dans un fichier de conf.d/.
Créer un site avec un server block
Le site de suivi des livraisons est d'abord une page statique, en attendant l'application PHP du chapitre 4. Crée sa racine et une page de test :
sudo mkdir -p /var/www/suivi/public
echo '<h1>Suivi des livraisons Sorbier</h1>' | sudo tee /var/www/suivi/public/index.html
Le dossier appartient à root et reste lisible par tous : Nginx (www-data) doit pouvoir lire les fichiers, jamais les modifier. Un site dont les fichiers appartiennent à www-data permet à quiconque exploite une faille de l'application de réécrire le site.
Crée ensuite /etc/nginx/sites-available/suivi :
server {
listen 80;
listen [::]:80;
server_name suivi.sorbier-logistique.fr;
root /var/www/suivi/public;
index index.html index.php;
access_log /var/log/nginx/suivi.access.log;
error_log /var/log/nginx/suivi.error.log;
location / {
try_files $uri $uri/ =404;
}
}
| Directive | Rôle |
|---|---|
listen 80 et listen [::]:80 | Le port d'écoute, en IPv4 et en IPv6 |
server_name | Le ou les noms auxquels ce bloc répond (comparés à l'en-tête Host de la requête) |
root | Le dossier dont les fichiers sont servis : /css/style.css devient /var/www/suivi/public/css/style.css |
index | Le fichier servi quand l'adresse désigne un dossier |
access_log, error_log | Des journaux propres au site, plus faciles à lire et à transmettre au SIEM |
try_files $uri $uri/ =404 | Essaie le fichier demandé, puis un dossier du même nom, sinon renvoie l'erreur 404 |
Active le site, vérifie la syntaxe, recharge :
sudo ln -s /etc/nginx/sites-available/suivi /etc/nginx/sites-enabled/
sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx
Tant que le nom public ne pointe pas encore vers le serveur, teste en fournissant toi-même l'en-tête Host :
curl -H "Host: suivi.sorbier-logistique.fr" http://192.168.10.225/
# <h1>Suivi des livraisons Sorbier</h1>
Comment Nginx choisit le site qui répond
Un même serveur peut héberger des dizaines de sites sur la même adresse IP et le même port. Pour chaque requête, Nginx procède en deux temps :
- il retient les server blocks dont la directive
listencorrespond à l'adresse et au port de la connexion ; - parmi eux, il choisit celui dont le
server_namecorrespond à l'en-têteHostenvoyé par le navigateur. Si aucun ne correspond, la requête va au serveur par défaut de ce port : celui qui portedefault_serverdans sonlisten, sinon le premier lu.
Sur un serveur exposé à Internet, les robots qui balaient les adresses IP envoient des requêtes sans nom, ou avec un nom quelconque. Si le site par défaut est ton site de production, ils le voient. Remplace donc le site par défaut de Debian par un bloc qui ne sert rien :
# /etc/nginx/sites-available/000-defaut
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/000-defaut /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
server_name _ est un nom qui ne correspond à rien, et 444 est un code propre à Nginx : il ferme la connexion sans rien renvoyer. Le chapitre 6 fait de même pour le port 443.
Les blocs location
Dans un server block, les blocs location disent quoi faire selon le chemin demandé. Leur ordre d'écriture compte moins que leur type :
| Syntaxe | Type | Exemple | Correspond à |
|---|---|---|---|
location = /sante | Égalité exacte | Page de contrôle d'un répartiteur | /sante seulement |
location ^~ /static/ | Préfixe prioritaire | Fichiers statiques | Tout ce qui commence par /static/, sans tester les expressions régulières |
location ~ \.php$ | Expression régulière, sensible à la casse | Scripts PHP | /index.php, /admin/liste.php |
location ~* \.(png|jpg|css|js)$ | Expression régulière, insensible à la casse | Ressources | /logo.PNG, /app.js |
location / | Préfixe | Tout le reste | Toute adresse |
Nginx choisit ainsi :
- une égalité exacte (
=) qui correspond gagne immédiatement ; - sinon, il cherche le plus long préfixe qui correspond ; s'il porte
^~, il gagne ; - sinon, il teste les expressions régulières dans l'ordre du fichier, et la première qui correspond gagne ;
- si aucune ne correspond, c'est le plus long préfixe trouvé à l'étape 2.
Exemple sur le site de suivi : interdire l'accès aux fichiers cachés (.git, .env) qui traîneraient dans la racine, et mettre en cache navigateur les ressources statiques.
# Fichiers et dossiers cachés : .git, .env, .htaccess
location ~ /\. {
deny all;
}
location ~* \.(css|js|png|jpg|svg|woff2)$ {
expires 7d;
access_log off;
}
Restreindre l'accès à une partie du site
Par adresse IP. La page de statistiques du site ne doit être vue que depuis le réseau d'administration :
location /stats/ {
allow 192.168.10.208/28;
deny all;
}
Les règles se lisent dans l'ordre et la première qui correspond s'applique : allow d'abord, deny all ensuite. Une requête interdite reçoit l'erreur 403.
Par mot de passe (authentification HTTP Basic). Le fichier des mots de passe se crée avec htpasswd, du paquet apache2-utils, et se range hors de la racine du site :
sudo apt install apache2-utils
sudo htpasswd -c -B /etc/nginx/htpasswd-stats amercier # -c crée le fichier, -B hache avec bcrypt
sudo chown root:www-data /etc/nginx/htpasswd-stats
sudo chmod 640 /etc/nginx/htpasswd-stats
location /stats/ {
satisfy all; # il faut l'adresse ET le mot de passe
allow 192.168.10.208/28;
deny all;
auth_basic "Statistiques";
auth_basic_user_file /etc/nginx/htpasswd-stats;
}
L'authentification Basic envoie l'identifiant et le mot de passe à chaque requête, seulement encodés en Base64 : elle n'est acceptable qu'en HTTPS. Pour une vraie application, préfère l'authentification de l'application elle-même, reliée à l'annuaire.
La taille des envois. Par défaut, Nginx refuse un corps de requête de plus de 1 Mo, avec l'erreur 413 Request Entity Too Large. Une application qui accepte des pièces jointes a besoin de plus, à régler dans son server block, en cohérence avec PHP (chapitre 4) :
client_max_body_size 20m;
Les journaux de Nginx
Chaque requête ajoute une ligne au journal d'accès, au format combined par défaut :
198.51.100.23 - - [21/Oct/2026:10:42:17 +0200] "GET /suivi.php?colis=SL-48213 HTTP/1.1" 200 3184 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
| Champ | Valeur | Sens |
|---|---|---|
| Adresse du client | 198.51.100.23 | Qui a fait la requête (derrière un reverse proxy, c'est l'adresse du proxy : voir le chapitre 10) |
| Utilisateur | - | Rempli seulement avec l'authentification HTTP |
| Date | [21/Oct/2026:10:42:17 +0200] | Avec le fuseau horaire |
| Requête | "GET /suivi.php?colis=SL-48213 HTTP/1.1" | Méthode, chemin avec ses paramètres, version de HTTP |
| Code de réponse | 200 | Succès ; 404 introuvable ; 403 interdit ; 5xx erreur du serveur |
| Taille | 3184 | Octets envoyés |
| Référent | "-" | La page d'où vient le visiteur |
| Agent | "Mozilla/5.0 …" | Le navigateur ou le robot |
Le journal d'erreurs, lui, dit pourquoi une requête a échoué : fichier absent, droit refusé, application injoignable. C'est le premier fichier à ouvrir quand un site renvoie une erreur 403, 404 ou 502.
sudo tail -f /var/log/nginx/suivi.error.log
# 2026/10/21 10:44:02 [error] 813#813: *51 open() "/var/www/suivi/public/favicon.ico" failed (2: No such file or directory)
Debian fait tourner ces journaux chaque jour et en garde 14, compressés (/etc/logrotate.d/nginx). Pour les conserver plus longtemps et pouvoir enquêter après un incident, ils sont envoyés au serveur central de Sorbier (chapitre 12).
Quelques commandes pour exploiter un journal d'accès :
# Les 10 adresses qui font le plus de requêtes
sudo awk '{print $1}' /var/log/nginx/suivi.access.log | sort | uniq -c | sort -rn | head
# Les requêtes en erreur 404 (le code est le 9e champ)
sudo awk '$9 == 404 {print $7}' /var/log/nginx/suivi.access.log | sort | uniq -c | sort -rn | head
Exercices
Exercice 1 sur 3 Quel bloc location ? Réussi À revoir
Un server block contient, dans cet ordre :
location / { … } # A
location /images/ { … } # B
location ~* \.(png|jpg)$ { … } # C
location ^~ /images/logos/ { … } # D
location = /images/plan.png { … } # E
Quel bloc traite chacune de ces requêtes : /images/photo.jpg, /images/logos/sorbier.png, /images/plan.png, /images/notice.pdf, /contact ?
Voir la réponseMasquer la réponse de l'exercice 1
| Requête | Bloc | Pourquoi |
|---|---|---|
/images/photo.jpg | C | Le plus long préfixe est B, sans ^~ : Nginx teste les expressions régulières, et C correspond |
/images/logos/sorbier.png | D | Le plus long préfixe est D, qui porte ^~ : les expressions régulières ne sont pas testées |
/images/plan.png | E | L'égalité exacte gagne avant tout le reste |
/images/notice.pdf | B | Aucune expression régulière ne correspond : le plus long préfixe, B, s'applique |
/contact | A | Seul le préfixe / correspond |
Le piège : croire que B traite toutes les adresses qui commencent par /images/. Sans ^~, une expression régulière qui correspond lui passe devant.
Tu avais trouvé ?
Exercice 2 sur 3 Le site de recette Réussi À revoir
Sorbier veut un site de recette, recette.sorbier-logistique.fr, racine /var/www/recette/public, sur srv-web. Il ne doit être accessible que depuis le réseau d'administration et le VLAN des postes, avec des journaux propres. Écris le server block et les commandes pour l'activer.
Voir la réponseMasquer la réponse de l'exercice 2
# /etc/nginx/sites-available/recette
server {
listen 80;
listen [::]:80;
server_name recette.sorbier-logistique.fr;
root /var/www/recette/public;
index index.html index.php;
access_log /var/log/nginx/recette.access.log;
error_log /var/log/nginx/recette.error.log;
allow 192.168.10.208/28; # ADMIN
allow 192.168.10.64/26; # POSTES
deny all;
location / {
try_files $uri $uri/ =404;
}
}
sudo mkdir -p /var/www/recette/public
sudo ln -s /etc/nginx/sites-available/recette /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Les directives allow et deny placées au niveau du server valent pour tous ses blocs location. Le filtrage de Nginx ne remplace pas celui du pare-feu, mais ici il est indispensable : la redirection du pare-feu porte sur l'adresse et le port, pas sur le nom. N'importe qui sur Internet peut envoyer une requête avec Host: recette.sorbier-logistique.fr à 203.0.113.17. La vraie protection serait d'héberger la recette sur un serveur interne, non publié.
Tu avais trouvé ?
Exercice 3 sur 3 Lire l'erreur Réussi À revoir
Après avoir activé un nouveau site, sudo nginx -t affiche : nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/suivi:2. Explique l'erreur et corrige-la.
Voir la réponseMasquer la réponse de l'exercice 3
Deux server blocks déclarent default_server sur le même port : le site par défaut (000-defaut, ou le default de Debian s'il n'a pas été retiré) et le site suivi, ligne 2. Il ne peut y avoir qu'un seul serveur par défaut par adresse et par port. Correction : retirer default_server de la directive listen du site suivi (listen 80;), puis relancer sudo nginx -t et recharger. Tant que nginx -t échoue, la configuration en service reste l'ancienne : aucun site n'est coupé.
Tu avais trouvé ?