Réglage des performances Nginx en pratique : gzip, cache et pools de connexions

La semaine dernière, une alerte : la page d’accueil d’un site e-commerce passait à 4 secondes de chargement. Dans Chrome DevTools : HTML 120 Ko, CSS et JS 350 Ko supplémentaires — tout en clair, sans compression. Pire encore : chaque requête atteignait le backend, avec un taux de hit cache ridicule de 12 %. Ce soir-là, après deux heures de réglage Nginx — gzip activé, stratégie de cache complétée, paramètres de pool de connexions — le lendemain matin la page d’accueil était autour de 1,6 s et le QPS backend avait presque chuté de moitié.
Ce genre de situation est très courant. Beaucoup installent Nginx et le laissent tourner : gzip désactivé par défaut, cache réduit à deux lignes, plafond de connexions par défaut. Au pic de trafic, le serveur suffoque.
Cet article rassemble les réglages Nginx que j’ai validés en production. La compression gzip réduit le volume transféré de 60 à 80 % ; Brotli économise encore 15 à 25 % ; à 95 % de hits cache, la pression backend baisse d’environ 90 % ; un pool de connexions bien dimensionné peut multiplier la concurrence par trois ou quatre ; avec Thread Pools et reuseport, le RPS d’une machine peut monter à 50K-80K. Je détaille chaque module, les pièges rencontrés et les chiffres mesurés.
Chapitre 1 : compression gzip/Brotli — réduire le volume transféré
Pourquoi gzip compte autant : un HTML de 100 Ko peut descendre à 20-25 Ko. Les 75-80 Ko économisés, c’est un chargement plus rapide pour l’utilisateur et moins de bande passante pour vous.
Je l’ai vraiment compris sur un projet client : plus de 60 % d’utilisateurs mobiles en 4G, page d’accueil à 3-4 s, taux de rebond à 70 %. Après activation de gzip, le volume transféré a chuté d’environ 70 %, premier écran autour de 1,5 s.
1.1 Configuration gzip de base : la faire tourner
La configuration gzip Nginx tient en quelques lignes :
http {
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}
gzip on est l’interrupteur. gzip_vary on ajoute Vary: Accept-Encoding pour que CDN et navigateurs sachent que la réponse dépend des capacités de compression du client — évite les incohérences de cache.
gzip_min_length à 1000 octets : en dessous, pas de compression (peu de gain, coût CPU). gzip_types liste les MIME à compresser ; par défaut seul text/html l’est — ajoutez CSS, JS, JSON, XML.
1.2 gzip avancé : niveau de compression et liste MIME
Le niveau est un compromis : gzip_comp_level va de 1 à 9, plus le chiffre est haut, plus la compression est forte et plus le CPU travaille.
Tests réalisés :
| Niveau | Taux sur HTML | CPU (ms) | Scénario |
|---|---|---|---|
| 1 | 65 % | 2 | CPU limité |
| 4 | 72 % | 3 | Équilibré (recommandé) |
| 6 | 75 % | 5 | Bande passante serrée (recommandé) |
| 9 | 78 % | 12 | Cas extrêmes |
En pratique, 4 et 6 conviennent le mieux. Le niveau 9 double le CPU pour quelques points de compression en plus — peu rentable.
Configuration gzip complète en production :
# Configuration compression gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
application/xhtml+xml
application/x-javascript;
gzip_disable "msie6";
gzip_proxied any : en reverse proxy, si le backend ne renvoie pas Content-Length, la compression est ignorée par défaut ; any force la compression des réponses éligibles.
gzip_disable "msie6" : compatibilité IE6. Aujourd’hui rare, je le laisse par habitude.
1.3 Quels types de fichiers gagnent le plus ?
D’après nos mesures :
| Type | Taille initiale | Après compression | Taux |
|---|---|---|---|
| HTML | 100 Ko | 20-25 Ko | 75-80 % |
| CSS | 80 Ko | 24-28 Ko | 65-70 % |
| JavaScript | 120 Ko | 36-42 Ko | 65-70 % |
| API JSON | 50 Ko | 20-25 Ko | 50-60 % |
| Image/vidéo | Déjà compressé | Peu ou pas | 0-5 % |
JPEG, PNG, MP4 sont déjà compressés : ajouter image/* ou video/* dans gzip_types peut augmenter la taille. J’ai vu un image/jpeg dans la liste faire grossir les images de 3 à 5 % — erreur classique quand on veut tout mettre dans gzip_types.
1.4 Brotli : 15 à 25 % de mieux que gzip
Brotli (Google) compresse mieux que gzip au même niveau, surtout pour le statique ; le support navigateur est large.
Attention : ce n’est pas un module Nginx par défaut — compilation ou module dynamique. J’utilise le module dynamique officiel sans recompiler Nginx :
# Charger les modules (si dynamiques)
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
http {
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json;
brotli_min_length 256;
}
Niveaux 1-11 ; je recommande 6. Au-delà, le temps de compression grimpe pour le dynamique. Statique : précompression niveau 11, Nginx sert le fichier .br.
Comparaison mesurée :
| Méthode | HTML 100 Ko → | Temps | Support navigateur |
|---|---|---|---|
| gzip (niveau 6) | 25 Ko | 5 ms | Quasi total |
| Brotli (niveau 6) | 18 Ko | 15 ms | 95 %+ |
| Brotli (précomp. 11) | 15 Ko | 0 | 95 %+ |
Conseil : dynamique en Brotli 4-6, statique en précompression. Si l’installation est pénible, gzip seul à ~75 % reste très solide.
Chapitre 2 : stratégie de cache — accélérer le contenu
Le cache est souvent le levier le plus direct : bien configuré, 95 % des requêtes peuvent sortir de Nginx sans toucher le backend. Trop de systèmes voient le backend à fond de charge alors que le cache Nginx est quasi inutilisé — pas absent, mal réglé.
2.1 proxy_cache ou fastcgi_cache ?
- proxy_cache : réponses du serveur amont, reverse proxy (Node.js, Python, Go…)
- fastcgi_cache : réponses FastCGI, typiquement PHP-FPM
PHP → fastcgi_cache ; Node/Python/Go → proxy_cache. La logique est quasi identique ; exemples ci-dessous en proxy_cache.
2.2 Configuration proxy_cache complète
D’abord le chemin de cache dans le bloc http :
http {
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=10g
inactive=60m
use_temp_path=off;
}
Détail :
levels=1:2: arborescence à deux niveaux, évite trop de fichiers dans un seul dossierkeys_zone=my_cache:10m: zone et mémoire des métadonnées (~80 000 clés pour 10m)max_size=10g: plafond total, éviction LRU au-delàinactive=60m: entrées non consultées depuis 60 min suppriméesuse_temp_path=off: écriture directe, sans déplacement depuis un répertoire temporaire
Activation dans server ou location :
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 1m;
proxy_cache_key $scheme$request_method$host$request_uri;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
}
}
proxy_cache_valid fixe la durée par code : 200/302 → 10 min, 404 → 1 min (limite les rafales malveillantes), autres → 1 min.
2.3 Clé de cache et invalidation
Clé explicite recommandée :
proxy_cache_key $scheme$request_method$host$request_uri;
Protocole, méthode, hôte et URI complets — utile avec plusieurs domaines ou GET/POST.
Invalidation :
- Expiration :
proxy_cache_valid - Contournement :
proxy_cache_bypass - Purge : Nginx Plus avec
proxy_cache_purge
Contournement par en-tête ou paramètre :
proxy_cache_bypass $http_x_nocache;
proxy_cache_bypass $arg_nocache;
Refresh : ?nocache=1 ou en-tête X-Nocache: 1.
2.4 Dégradation : servir quand le backend tombe
proxy_cache_use_stale renvoie une entrée expirée si le backend est en erreur ou timeout :
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
Lors d’un Double 11, des 502 intermittents sur l’API : grâce à cette option, l’expérience utilisateur est restée correcte avec du cache périmé de quelques minutes, puis mise à jour automatique au retour du backend.
Comparaison :
| Indicateur | Sans cache | Hit cache | Mode stale |
|---|---|---|---|
| Temps de réponse | 150-200 ms | 5-10 ms | 5-10 ms |
| QPS backend | 1000 | 50 | 0 |
| Expérience | Normale | Normale | Légèrement plus lente |
Un hit fait passer ~200 ms à 5-10 ms — gain évident.
2.5 Micro-cache : accélérer le dynamique
Le dynamique peut être micro-caché 1-5 secondes pour absorber les pics.
Sur une page d’accueil e-commerce (reco temps réel, stock), pendant une promo le backend ne tenait pas. Micro-cache 5 s :
proxy_cache_path /var/cache/nginx/micro levels=1:2 keys_zone=micro:10m max_size=1g;
location / {
proxy_cache micro;
proxy_cache_valid 200 5s;
proxy_cache_lock on;
proxy_cache_background_update on;
}
proxy_cache_lock on : à l’expiration, une seule requête va au backend, les autres gardent l’ancienne entrée — évite la tempête au renouvellement.
Résultat : TTFB ~800 ms → ~5 ms, QPS backend ~2000 → ~400. Cinq secondes de décalage sont à peine perceptibles.
2.6 Requêtes conditionnelles
proxy_cache_revalidate utilise If-Modified-Since / If-None-Match : un 304 évite de retélécharger tout le corps, seules les métadonnées de cache sont mises à jour.
proxy_cache_revalidate on;
Utile quand le backend envoie de gros contenus peu modifiés.
Chapitre 3 : pools de connexions — haute concurrence
gzip et cache optimisent le transfert ; les pools portent la capacité. Par défaut, un worker Nginx plafonne souvent à 1024 connexions — insuffisant sous charge.
3.1 worker_connections : calculer le plafond
Concurrence max = worker_processes × worker_connections
8 cœurs, worker_processes 8 (ou auto), worker_connections 4096 :
Concurrence max = 8 × 4096 = 32768
Chaque requête utilise en général deux connexions (client → Nginx, Nginx → backend) : comptez environ la moitié en requêtes simultanées réelles.
Bloc events :
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
Sous Linux, epoll est souvent par défaut ; multi_accept on accepte plusieurs connexions d’un coup et réduit la file d’attente.
3.2 keepalive côté client
Le TCP triple handshake coûte cher ; le keepalive réutilise la liaison client–Nginx.
http {
keepalive_timeout 65;
keepalive_requests 1000;
}
keepalive_timeout 65 : 60-75 s est un bon compromis. keepalive_requests 1000 : plafond de requêtes par connexion — 1000 est un repère stable en production.
Pour limiter la durée de vie totale d’une connexion :
keepalive_time 1h;
3.3 keepalive upstream : pool vers le backend
Nginx peut aussi réutiliser les connexions vers l’amont :
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 64;
keepalive_timeout 60s;
keepalive_requests 1000;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
keepalive 64 : pool d’connexions idle — souvent 4 à 8 fois le nombre de serveurs backend.
Obligatoire : proxy_http_version 1.1 et proxy_set_header Connection "". Sans ces deux lignes dans location, le keepalive upstream ne fonctionne pas.
Comparaison mesurée :
| Config | Connexions créées/min | CPU | Scénario |
|---|---|---|---|
| Sans keepalive upstream | 6000 | Élevé | Faible trafic |
| keepalive 32 | 3000 | Moyen | Trafic moyen |
| keepalive 64 | 1500 | Faible | Fort trafic |
Environ 50 % de connexions en moins — crucial sous forte charge.
3.4 Descripteurs de fichiers : limite système
worker_connections 4096 ne sert à rien si ulimit -n reste à 1024.
ulimit -n
Si la valeur est < 65536, ajustez /etc/security/limits.conf :
* soft nofile 65536
* hard nofile 65536
Et dans Nginx (bloc principal) :
worker_rlimit_nofile 65536;
3.5 Tableau de paramètres recommandés
| Paramètre | Faible (<1000 QPS) | Moyen (1000-5000) | Élevé (>5000) |
|---|---|---|---|
| worker_processes | auto | auto | auto |
| worker_connections | 1024 | 2048 | 4096 |
| keepalive_timeout | 60 | 65 | 75 |
| keepalive_requests | 100 | 500 | 1000 |
| upstream keepalive | 16 | 32 | 64 |
| worker_rlimit_nofile | 4096 | 8192 | 65536 |
Point de départ uniquement — validez avec wrk ou ab (connexions, latence, courbes).
Chapitre 4 : optimisations avancées — Thread Pools et reuseport
Les trois chapitres précédents couvrent la plupart des cas. Pour viser >50K RPS par machine ou une latence très serrée, deux réglages supplémentaires.
4.1 Thread Pools : dépasser le goulot sendfile
Le modèle événementiel suffit souvent ; en fort trafic statique, sendfile lit et envoie dans le même worker — un disque lent bloque le worker.
Les Thread Pools déportent lecture et envoi ; le worker orchestre. Cas officiel Nginx : ~×9 sur téléchargement de fichiers 1 Mo limités par le disque.
http {
thread_pool default threads=32 max_queue=65536;
aio threads=default;
sendfile_max_chunk 512k;
}
threads=32, file max_queue=65536, aio threads=default, chunks sendfile_max_chunk 512k.
Peu utile si tout est dynamique (API) : surcoût de changement de contexte. Privilégiez statique et gros fichiers.
4.2 reuseport (Socket Sharding)
Depuis Nginx 1.9.1, reuseport donne à chaque worker son socket d’écoute — moins de mutex sur accept.
server {
listen 80 reuseport;
}
Données officielles :
| Indicateur | Sans reuseport | Avec reuseport |
|---|---|---|
| Latence moyenne | 15,65 ms | 12,35 ms |
| Écart-type latence | 3,5 ms | 1,2 ms |
| Répartition | Inégale | Équilibrée |
~21 % de latence en moins et jitter réduit — surtout au-delà de ~20K QPS.
4.3 open_file_cache
Pour le statique, cache des descripteurs et métadonnées :
http {
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
Efficace sur site statique ; à éviter si les fichiers changent souvent (risque de contenu obsolète).
Chapitre 5 : modèle de configuration — prêt pour la production
Modèle intégré (scénario fort trafic) :
# Modèle nginx.conf production (fort trafic)
user nginx;
worker_processes auto;
worker_rlimit_nofile 65536;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
application/xml+rss application/xhtml+xml;
gzip_disable "msie6";
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=10g
inactive=60m
use_temp_path=off;
keepalive_timeout 65;
keepalive_requests 1000;
keepalive_time 1h;
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 64;
keepalive_timeout 60s;
keepalive_requests 1000;
}
server {
listen 80 reuseport;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key $scheme$request_method$host$request_uri;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
proxy_cache_revalidate on;
add_header X-Cache-Status $upstream_cache_status;
}
}
}
Trois profils métier
E-commerce : pages changeantes — cache 10-15 min, keepalive upstream élevé, micro-cache 1-3 s en promo.
API : données fraîches — proxy_cache_valid 1-5 min, gzip sur JSON indispensable, Brotli si possible.
Site statique : cache long (1 h+), gzip maximal sur texte, open_file_cache et Thread Pools pertinents.
Comparatif performances (mis à jour)
| Réglage | Avant | Après | Gain |
|---|---|---|---|
| Volume HTML (gzip) | 100 Ko | 25 Ko | 75 % ↓ |
| Volume HTML (Brotli) | 100 Ko | 18 Ko | 82 % ↓ |
| Temps API (hit cache) | 200 ms | 8 ms | 96 % ↓ |
| Connexions concurrentes | 1000 | 4000 | ×4 |
| RPS (machine optimisée) | 10K | 50K-80K | ×5-8 |
| TTFB (reuseport) | 15,65 ms | 12,35 ms | 21 % ↓ |
Chiffres issus de mesures terrain et documentation officielle — validez sur votre infra.
Chapitre 6 : problèmes fréquents et dépannage
Q : pas de Content-Encoding: gzip
gzip ondans le blochttpgzip_typescouvre votre MIME- taille >
gzip_min_length
Test : curl -H "Accept-Encoding: gzip" -I http://votre-site.com
Q : X-Cache-Status surtout MISS
- Clé de cache trop fine
proxy_cache_validtrop courtCache-Control: no-cacheouSet-Cookiecôté backend
Q : 502, worker_connections insuffisant
Log : worker_connections are not enough → augmenter la valeur, vérifier les fuites keepalive, scaler horizontalement.
Q : conflit gzip et sendfile ?
Non. gzip compresse la réponse ; sendfile optimise le transfert fichier. Dynamique compressé en mémoire ; statique précompressé peut utiliser sendfile.
Q : upstream keepalive inactif
Manque proxy_http_version 1.1 ou proxy_set_header Connection "" dans location, pas dans upstream.
Q : reuseport — « duplicate listen options »
reuseport une seule fois par directive listen concernée.
Q : OOM, mémoire élevée
Réduire keys_zone, volume de cache, ou taille des pools keepalive ; ajouter de la RAM si besoin.
Pour conclure
Le cœur du réglage Nginx : compression, cache, pools de connexions. Brotli, micro-cache, Thread Pools et reuseport peuvent porter le RPS de 10K vers 50K-80K.
Ordre conseillé :
- gzip — rapide, gain immédiat
- cache — selon le métier, en une journée
- pools — après stabilisation du trafic, avec bench
- avancé — Thread Pools / reuseport si le volume l’exige
Après chaque changement : wrk ou ab, latence, QPS, taux d’erreur — les données, pas l’intuition.
Checklist finale :
- gzip activé, MIME complets
- gzip_comp_level 4-6
- Brotli si module disponible
- proxy_cache_path dimensionné
- proxy_cache_valid adapté au métier
- micro-cache si pic sur contenu dynamique
- proxy_cache_use_stale configuré
- worker_connections ≥ 4096
- keepalive_timeout 60-75 s
- upstream keepalive + HTTP/1.1 + Connection vide
- reuseport si fort trafic
- worker_rlimit_nofile 65536
- X-Cache-Status pour le debug
Des questions en commentaire — je réponds quand je peux.
FAQ
La compression gzip ne s'applique pas, pas de Content-Encoding: gzip dans les en-têtes — que faire ?
Faible taux de hit cache, X-Cache-Status surtout en MISS — comment diagnostiquer ?
worker_connections insuffisant, erreurs 502 — comment résoudre ?
Pourquoi upstream keepalive ne fonctionne-t-il pas ?
Comment choisir entre Brotli et gzip ?
Quels paramètres selon le volume de trafic ?
11 min de lecture · Publié le: 15 mai 2026 · Mis à jour le: 27 juil. 2026
Guide pratique Nginx
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Upstream dynamique Nginx : découverte de services en temps réel avec Lua
Architecture en trois couches OpenResty pour upstream dynamique, comparaison des solutions de health check, code d'intégration Consul/Nacos/etcd pour la découverte de services en environnement conteneurisé.
Partie 5 sur 6
Suivant
C’est le dernier article publié dans cette série pour le moment.



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire