Skip to Content

Chapitre 7.6 — Perf, cache & logs

⏱️ TL;DR — Trois réglages qui changent tout, plus l’outil de survie du dépanneur. Perf : active la compression gzip (et sache que Brotli existe, en module à part) pour alléger le texte transféré. Cache : pose expires et Cache-Control sur les assets versionnés pour que le navigateur ne les redemande pas. Le piège des uploads : par défaut Nginx refuse les corps de requête trop gros et renvoie un 413client_max_body_size le règle. Enfin, les logs : access.log (chaque requête) et error.log (les problèmes) sont ta première source de vérité ; tail -f pour regarder en direct, et savoir où regarder pour un 502, un 504 ou un 413. On boucle avec un mot sur limit_req (limitation de débit) et le rappel du rechargement propre.

🎯 Objectifs

  • Activer la compression gzip et situer Brotli.
  • Poser un cache navigateur correct sur les assets.
  • Éviter le 413 des gros uploads avec client_max_body_size.
  • Lire les logs Nginx et savoir chercher selon l’erreur.
  • Découvrir limit_req (limitation de débit) en survol sécurité.
  • Recharger proprement et clore la partie.

Compression : gzip (et un mot sur Brotli)

Le HTML, le CSS, le JS et le JSON sont du texte : très compressibles. Activer gzip fait que Nginx compresse ces réponses avant de les envoyer, divisant souvent leur poids par 3 ou 4 — pages plus rapides, bande passante économisée. Ça se règle dans le contexte http (donc nginx.conf), une fois pour tout le serveur.

# dans le contexte http de nginx.conf gzip on; # active la compression gzip_comp_level 5; # niveau 1 (rapide) à 9 (petit) ; 4-6 est un bon compromis gzip_min_length 1024; # ne compresse pas les tout petits fichiers (inutile) gzip_types text/plain text/css application/javascript application/json image/svg+xml; # les types texte à compresser (le HTML l'est déjà par défaut)

Détails : gzip on; active le tout ; gzip_comp_level arbitre entre CPU et taille (un niveau moyen suffit — au-delà, on brûle du CPU pour quelques octets) ; gzip_min_length évite de compresser des fichiers minuscules (où l’entête gzip coûterait plus que le gain) ; gzip_types liste les types MIME à compresser (le text/html est inclus d’office). On ne compresse pas les images JPEG/PNG/WebP ni les vidéos : elles sont déjà compressées, gzip ne ferait que gaspiller du CPU.

Brotli est un algorithme plus récent, souvent 20 à 30 % plus efficace que gzip sur le texte, supporté par tous les navigateurs modernes. Nginx ne l’inclut pas par défaut : il faut un module séparé (ngx_brotli), à compiler ou installer selon la distribution. À connaître ; gzip reste un excellent défaut sans rien installer.

💡 Réflexe — Compresse le texte (HTML, CSS, JS, JSON, SVG), jamais ce qui est déjà compressé (JPEG, PNG, WebP, MP4, ZIP). Compresser un JPEG, c’est dépenser du CPU pour grossir légèrement le fichier. La liste gzip_types doit refléter ça. Vérifie que ça marche : curl -H "Accept-Encoding: gzip" -I https://formacampus.fr doit montrer un en-tête Content-Encoding: gzip sur les réponses texte.

Cache navigateur des assets

On l’a vu au chapitre 7.3 : les assets versionnés par hash peuvent être cachés très longtemps, puisqu’un changement de contenu change leur URL. Le rappel, appliqué ici côté perf globale.

location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|webp|woff2?)$ { expires 1y; # en-têtes Expires + Cache-Control add_header Cache-Control "public, immutable"; # "immutable" : ne revalide même pas }

expires 1y; pose la durée ; Cache-Control "public, immutable" dit au navigateur que le fichier est partageable (proxys, CDN) et ne changera jamais à cette URL (immutable lui évite même de revalider). Combiné à un HTML non caché, tu obtiens un site rapide et qui se met à jour instantanément : le nouveau HTML pointe vers les nouveaux hashes, que le navigateur télécharge, pendant que les anciens assets restent en cache pour ce qui n’a pas changé.

Le piège des uploads : client_max_body_size et le 413

Par défaut, Nginx limite la taille du corps d’une requête (souvent 1 Mo). Au-delà, il refuse avec un 413 Request Entity Too Largeavant même que la requête n’atteigne ton app. Symptôme classique : « l’upload d’un fichier ou d’une image un peu lourde échoue, mais mon code applicatif est correct ». La cause est Nginx, pas ton app : c’est le proxy qui coupe.

# au niveau http (global), server (un site) ou location (un chemin précis) client_max_body_size 25m; # autorise les corps de requête jusqu'à 25 Mo

client_max_body_size 25m; relève le plafond à 25 Mo. Tu peux le poser globalement (contexte http), par site (server) ou par chemin (location) — par exemple un plafond bas partout, relevé uniquement sur location /upload/. Choisis une valeur qui couvre tes vrais besoins sans ouvrir la porte à des corps démesurés.

⚠️ Piège — Le 413 des uploads fait perdre un temps fou parce qu’on cherche le bug dans l’app. Réflexe : un upload qui échoue au-delà d’une certaine taille = client_max_body_size trop bas dans Nginx. Et attention, il faut souvent l’aligner avec la limite de ton app (upload_max_filesize/post_max_size côté PHP, la config de body parser côté Node) : le plus bas des deux gagne. Règle les deux pour un upload de N Mo, sinon l’un ou l’autre coupera.

Buffers, en survol

Nginx bufferise par défaut les réponses des apps proxifiées : il lit la réponse de l’app dans des tampons, ce qui libère l’app plus vite et protège des clients lents. Des directives comme proxy_buffering, proxy_buffers et proxy_buffer_size ajustent ce comportement. Dans 99 % des cas, les défauts conviennent — ne les touche que si tu as un cas précis (streaming d’événements où tu veux proxy_buffering off;, ou en-têtes anormalement gros). Le savoir existe ; n’y va que sur besoin réel, et lis la doc avant.

Les logs : ta première source de vérité

Nginx écrit deux journaux dans /var/log/nginx/. Ce sont tes yeux sur ce qui se passe.

  • access.log : une ligne par requête reçue — IP du client, date, méthode, chemin, code de statut (200, 404, 502…), taille, User-Agent. C’est là que tu vois le trafic réel et les codes renvoyés.
  • error.log : les problèmes — erreurs de config, permissions refusées, upstream injoignable (502), timeout (504). C’est le premier endroit à regarder quand quelque chose casse.
# suivre les requêtes en direct (Ctrl+C pour arrêter) sudo tail -f /var/log/nginx/access.log # suivre les erreurs en direct pendant qu'on reproduit le bug sudo tail -f /var/log/nginx/error.log # chercher les 502 récents dans les accès sudo grep ' 502 ' /var/log/nginx/access.log | tail -n 20

tail -f affiche en continu les nouvelles lignes : tu lances la commande, tu reproduis le problème dans le navigateur, et tu vois en temps réel ce que Nginx enregistre. C’est le geste de débogage à avoir dans les doigts.

Le format de l’access.log est configurable (log_format dans nginx.conf) ; le format combined par défaut suffit largement pour commencer. Tu peux aussi désactiver le log sur des chemins bavards et sans intérêt (access_log off; dans un location d’assets) pour garder des journaux lisibles.

Où regarder selon l’erreur

ErreurCe que ça veut direOù regarder d’abord
502 Bad GatewayNginx ne joint pas l’app (tombée, mauvais port)error.log + systemctl status <app> + journalctl -u <app>
504 Gateway TimeoutL’app répond trop lentement (timeout dépassé)error.log (mention timed out) + perfs de l’app / DB
413 Too LargeCorps de requête trop gros pour Nginxerror.log + relever client_max_body_size
403 ForbiddenDroits ou chemin sur du statiqueerror.log (Permission denied) + droits www-data
404 Not FoundFichier absent ou try_files mal régléerror.log + vérifier root/try_files

💡 Réflexe — Face à n’importe quelle erreur Nginx, la première commande est sudo tail -f /var/log/nginx/error.log, puis tu reproduis le bug. La ligne qui apparaît te donne le fichier, le port, ou la raison exacte — bien plus vite que deviner. Ne modifie jamais la config à l’aveugle : lis d’abord ce que le serveur te dit dans ses logs.

🧭 Sur FormaCampus — Le jour où les formateurs se plaignent que l’upload d’un support de cours PDF de 12 Mo échoue (« erreur au téléversement »), l’équipe ouvre l’error.log : client intended to send too large body. Diagnostic immédiat — 413, client_max_body_size par défaut trop bas. Correctif : client_max_body_size 50m; sur le location d’upload de l’API, aligné avec post_max_size/upload_max_filesize côté PHP-FPM (Partie 10), nginx -t, reload. Sans les logs, ils auraient cherché des heures dans le code Symfony. Les logs d’abord.

Limitation de débit : limit_req en survol

Nginx peut limiter le débit de requêtes par IP — utile contre le bruteforce d’un formulaire de login ou l’abus d’une API. On déclare une zone (au niveau http) puis on l’applique sur un location.

# dans http : une zone qui autorise ~10 requêtes/seconde par IP limit_req_zone $binary_remote_addr zone=login:10m rate=10r/s; # dans un server/location sensible : appliquer la zone location /api/login { limit_req zone=login burst=20 nodelay; # tolère un pic de 20, puis limite proxy_pass http://127.0.0.1:8000; }

limit_req_zone définit une zone nommée login qui compte les requêtes par IP ($binary_remote_addr) avec un plafond de 10r/s ; limit_req ... burst=20 l’applique en tolérant une rafale de 20 avant de freiner. C’est un complément au pare-feu et à fail2ban (Partie 5), pas un remplacement — on le survole ici ; la sécurité fait l’objet de sa propre progression dans le cours.

🔒 Sécurité — La limitation de débit protège tes points sensibles (login, endpoints coûteux) contre le bruteforce et le déni de service applicatif, au bord, avant même que la requête n’atteigne ton app. Combinée à fail2ban (qui bannit les IP abusives au niveau pare-feu) et à des en-têtes de sécurité, elle fait de Nginx une vraie première ligne de défense — pas seulement un aiguilleur.

Recharger proprement et clore la partie

Comme toujours : chaque modif de perf, de cache ou de logs passe par le rituel. nginx -t teste, reload applique sans couper les connexions.

sudo nginx -t && sudo systemctl reload nginx

📚 La docgzip relève du module ngx_http_gzip_module ; client_max_body_size, client_body_* du core module ; limit_req du ngx_http_limit_req_module ; les logs de ngx_http_log_module. Toutes sur nginx.org, avec contexte de validité et défauts. Pour Brotli, c’est le dépôt du module ngx_brotli qui fait référence.

Avec cette partie, ta façade web est complète : Nginx installé et structuré, statique servi, apps proxifiées avec les bons en-têtes, plusieurs sites aiguillés par domaine, perf et logs maîtrisés. Il manque une chose pour une vraie prod : le cadenas. Tout ce que tu as écrit ici en listen 80; va accueillir le HTTPS — c’est l’objet de la partie suivante.

✏️ Exercices

Exercice 1 — Compresser juste ce qu’il faut. Un collègue ajoute image/png et video/mp4 à sa liste gzip_types « pour tout accélérer ». Pourquoi est-ce une mauvaise idée, et que devrait contenir la liste ?

✅ Solution

PNG et MP4 sont déjà compressés : gzip ne gagne rien, voire grossit légèrement le fichier, tout en brûlant du CPU à chaque requête. La liste doit contenir le texte compressible : text/plain, text/css, application/javascript, application/json, image/svg+xml (le text/html est inclus par défaut). On ne compresse jamais images et vidéos déjà encodées.

Exercice 2 — Débogue le 413. Un formulaire d’upload accepte les petits fichiers mais échoue au-delà de ~1 Mo, avec une erreur côté navigateur. nginx -t est OK, l’app n’a pas de bug visible. Que regardes-tu, quelle est la cause, et que corriges-tu (côté Nginx et au-delà) ?

✅ Solution

Je regarde /var/log/nginx/error.log : il montre client intended to send too large body. Cause : client_max_body_size par défaut (souvent 1 Mo) → Nginx renvoie 413 avant que l’app ne voie la requête. Correction Nginx : client_max_body_size 25m; (ou la taille voulue) sur le location/server d’upload, puis nginx -t && systemctl reload nginx. Au-delà : aligner la limite de l’app (PHP post_max_size/upload_max_filesize, ou body parser Node) — le plus bas des deux gagne, donc il faut régler les deux.

Exercice 3 — Où est le maillon fautif ? Trois incidents séparés : (a) 502 sur tout le site, (b) 504 seulement sur une page de rapport, (c) 403 sur les images. Pour chacun, dis où tu regardes en premier et la cause probable.

✅ Solution

(a) 502 : l’app proxifiée est tombée ou écoute sur le mauvais port → error.log + systemctl status <app> + ss -tulpn. (b) 504 : cette page fait une requête trop longue (calcul/DB lent) qui dépasse le proxy_read_timeouterror.log (timed out) + optimiser l’app ou relever le timeout avec prudence. (c) 403 sur images : droits sur le statique → error.log (Permission denied) + vérifier que www-data peut lire les fichiers et traverser les dossiers. Dans les trois cas, l’error.log est le point de départ.

🧠 Quiz de révision

1. Quels types de fichiers faut-il compresser avec gzip, et lesquels surtout pas ?

Compresser le texte : HTML, CSS, JS, JSON, SVG. Ne pas compresser ce qui est déjà compressé : JPEG, PNG, WebP, MP4, ZIP — gzip y gaspille du CPU pour un gain nul ou négatif. La liste gzip_types doit refléter ça.

2. Qu’est-ce que Brotli et pourquoi n’est-il pas actif par défaut ?

Un algorithme de compression plus récent, souvent 20-30 % plus efficace que gzip sur le texte, supporté par les navigateurs modernes. Nginx ne l’inclut pas d’origine : il faut le module ngx_brotli (compilé ou installé à part). gzip reste un très bon défaut sans rien ajouter.

3. Un upload échoue au-delà d’une certaine taille. Cause et correctif ?

Nginx limite la taille du corps de requête (défaut souvent ~1 Mo) et renvoie 413 au-delà, avant l’app. Correctif : relever client_max_body_size au niveau voulu, et aligner la limite côté app (PHP/Node) — le plus bas des deux l’emporte.

4. Quelle est la différence entre access.log et error.log ?

access.log enregistre chaque requête (IP, chemin, code de statut, taille) — le trafic réel. error.log enregistre les problèmes (config, permissions, upstream injoignable, timeouts). Face à une panne, on ouvre error.log en premier, souvent avec tail -f en reproduisant le bug.

5. À quoi sert limit_req et avec quoi se combine-t-il ?

À limiter le débit de requêtes par IP (ex. protéger un /login du bruteforce), au bord, avant l’app. On déclare une zone (limit_req_zone) puis on l’applique (limit_req). Il complète le pare-feu et fail2ban — il ne les remplace pas.


Fin de la Partie 7. Ta façade web est en place, mais encore en clair. On la passe en HTTPS : Partie 8 — HTTPS avec Let’s Encrypt — certificats gratuits, renouvellement automatique et redirection HTTP → HTTPS.

Last updated on