Changer le thème

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

Easton editorial illustration: before-and-after response pipe, gzip compression module, cache chamber, connection pool manifold

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 :

NiveauTaux sur HTMLCPU (ms)Scénario
165 %2CPU limité
472 %3Équilibré (recommandé)
675 %5Bande passante serrée (recommandé)
978 %12Cas 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 :

TypeTaille initialeAprès compressionTaux
HTML100 Ko20-25 Ko75-80 %
CSS80 Ko24-28 Ko65-70 %
JavaScript120 Ko36-42 Ko65-70 %
API JSON50 Ko20-25 Ko50-60 %
Image/vidéoDéjà compresséPeu ou pas0-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éthodeHTML 100 Ko →TempsSupport navigateur
gzip (niveau 6)25 Ko5 msQuasi total
Brotli (niveau 6)18 Ko15 ms95 %+
Brotli (précomp. 11)15 Ko095 %+

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 dossier
  • keys_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ées
  • use_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 :

  1. Expiration : proxy_cache_valid
  2. Contournement : proxy_cache_bypass
  3. 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 :

IndicateurSans cacheHit cacheMode stale
Temps de réponse150-200 ms5-10 ms5-10 ms
QPS backend1000500
ExpérienceNormaleNormaleLé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 :

ConfigConnexions créées/minCPUScénario
Sans keepalive upstream6000ÉlevéFaible trafic
keepalive 323000MoyenTrafic moyen
keepalive 641500FaibleFort 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ètreFaible (<1000 QPS)Moyen (1000-5000)Élevé (>5000)
worker_processesautoautoauto
worker_connections102420484096
keepalive_timeout606575
keepalive_requests1005001000
upstream keepalive163264
worker_rlimit_nofile4096819265536

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 :

IndicateurSans reuseportAvec reuseport
Latence moyenne15,65 ms12,35 ms
Écart-type latence3,5 ms1,2 ms
RépartitionIné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églageAvantAprèsGain
Volume HTML (gzip)100 Ko25 Ko75 % ↓
Volume HTML (Brotli)100 Ko18 Ko82 % ↓
Temps API (hit cache)200 ms8 ms96 % ↓
Connexions concurrentes10004000×4
RPS (machine optimisée)10K50K-80K×5-8
TTFB (reuseport)15,65 ms12,35 ms21 % ↓

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

  1. gzip on dans le bloc http
  2. gzip_types couvre votre MIME
  3. 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_valid trop court
  • Cache-Control: no-cache ou Set-Cookie cô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é :

  1. gzip — rapide, gain immédiat
  2. cache — selon le métier, en une journée
  3. pools — après stabilisation du trafic, avec bench
  4. 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 ?
Vérifiez trois points : 1) gzip on est-il dans le bloc http ; 2) gzip_types inclut-il le type MIME de la réponse ; 3) le volume dépasse-t-il gzip_min_length. Testez avec curl -H "Accept-Encoding: gzip" -I.
Faible taux de hit cache, X-Cache-Status surtout en MISS — comment diagnostiquer ?
Causes fréquentes : clé de cache mal conçue, proxy_cache_valid trop court, backend renvoie Cache-Control: no-cache ou Set-Cookie. Inspectez les en-têtes de réponse pour exclure les directives anti-cache.
worker_connections insuffisant, erreurs 502 — comment résoudre ?
Consultez les logs d'erreur Nginx ; le message "worker_connections are not enough" indique un dépassement de concurrence. Augmentez worker_connections, vérifiez les fuites de connexion, ou ajoutez des serveurs en équilibrage de charge.
Pourquoi upstream keepalive ne fonctionne-t-il pas ?
Deux erreurs courantes : 1) proxy_http_version 1.1 manquant — HTTP/1.0 ne gère pas keepalive par défaut ; 2) proxy_set_header Connection "" manquant. Ces deux lignes doivent être dans le bloc location.
Comment choisir entre Brotli et gzip ?
Brotli compresse 15 à 25 % de mieux que gzip mais nécessite un module supplémentaire. Contenu dynamique : Brotli niveau 4-6 ; ressources statiques : précompression niveau 11. Si la compilation Nginx est lourde, gzip niveau 6 suffit (~75 % de compression).
Quels paramètres selon le volume de trafic ?
Faible trafic (<1000 QPS) : worker_connections 1024, keepalive 16 ; moyen (1000-5000 QPS) : 2048, keepalive 32 ; élevé (>5000 QPS) : 4096, keepalive 64. Ajustez avec des tests de charge.

11 min de lecture · Publié le: 15 mai 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog