Changer le thème

Optimisation des performances Nginx : gzip, cache et pool de connexions

Easton editorial illustration: service mesh rail yard

La semaine dernière, une alerte : le temps de chargement de la page d’accueil d’un site e-commerce avait grimpé à 4 secondes. Chrome DevTools montrait un HTML de 120 Ko, CSS et JS à 350 Ko — tout en clair, sans compression. Pire : chaque requête frappait le backend, avec un taux de hits cache pitoyable de 12 %. Après une soirée de travail, j’ai passé 2 heures sur la config Nginx : gzip activé, stratégie de cache complétée, paramètres de pool ajustés. Le lendemain matin, la page d’accueil était à 1,6 s et le QPS backend avait chuté de près 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 de performance Nginx que j’ai validés en production. La compression gzip peut réduire le volume transféré de 60 à 80 % ; avec 95 % de hits cache, la charge backend baisse d’environ 90 % ; un pool de connexions bien dimensionné peut multiplier la concurrence par trois ou quatre. Je détaille chaque module, les pièges que j’ai rencontrés et les chiffres mesurés.

Chapitre 1 : configuration gzip — réduire le volume transféré

Pourquoi gzip compte autant ? Imaginez un HTML de 100 Ko : compressé, il peut tomber à 20-25 Ko. Les 75-80 Ko économisés, c’est un chargement plus rapide pour l’utilisateur et moins de coût bande passante pour vous.

J’ai pris conscience du problème sur un projet client : plus de 60 % d’utilisateurs mobiles, souvent en 4G. La page d’accueil mettait 3-4 s, taux de rebond à 70 %. Avec gzip, le volume transféré a chuté d’environ 70 % et le first paint est passé autour de 1,5 s.

1.1 Configuration de base : démarrer simplement

La config gzip Nginx n’est pas complexe — quelques lignes suffisent :

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 active la compression. gzip_vary on ajoute Vary: Accept-Encoding dans la réponse : le CDN et le navigateur savent que le contenu dépend de la capacité de compression du client, ce qui évite les incohérences de cache.

gzip_min_length 1000 signifie : pas de compression sous 1 Ko — le gain est faible et le CPU gaspillé. gzip_types liste les MIME à compresser ; par défaut seul text/html l’est, il faut ajouter CSS, JS, JSON, XML, etc.

1.2 Configuration avancée : niveau de compression et types MIME

Le niveau de compression est un compromis. gzip_comp_level va de 1 à 9 : plus le chiffre est élevé, plus le taux de compression monte, mais aussi la charge CPU.

Tests que j’ai réalisés :

NiveauTaux HTMLTemps CPU (ms)Scénario recommandé
165 %2CPU limité
472 %3Équilibré (recommandé)
675 %5Bande passante limitée (recommandé)
978 %12Cas extrêmes

Les niveaux 4 et 6 conviennent dans la plupart des cas. Le niveau 9 double la charge CPU pour quelques points de compression en plus — peu rentable.

Configuration gzip complète que j’utilise 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 est souvent oublié. En reverse proxy, si le backend ne renvoie pas Content-Length, la compression peut être ignorée ; any force la compression des réponses éligibles.

gzip_disable "msie6" contourne les bugs gzip d’IE6. Aujourd’hui IE6 a disparu ; la ligne peut partir, je la garde par habitude.

1.3 Quels types de fichiers profitent le plus ?

D’après nos mesures en production, l’effet varie fortement :

TypeTaille originaleAprè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 utile0-5 %
75-80 %
Taux de compression HTML
Source: Données mesurées en production

Images et vidéos (JPEG, PNG, MP4) sont déjà compressées ; gzip peut même grossir le fichier. Ne mettez pas image/* ni video/* dans gzip_types.

Une fois, en dépannage, gzip_types incluait image/jpeg : les images grossissaient de 3 à 5 %. Erreur classique — à mes débuts, j’avais tendance à tout lister.

Chapitre 2 : stratégie de cache — accélérer le contenu

Le cache est le levier le plus direct en tuning perf. Bien configuré, 95 % des requêtes peuvent être servies par Nginx sans toucher le backend. Trop de systèmes font tourner le backend à fond alors que le cache Nginx est quasi inutilisé — pas parce qu’il est off, mais mal réglé.

2.1 proxy_cache ou fastcgi_cache ?

Nginx propose deux mécanismes :

  • proxy_cache : met en cache la réponse du serveur amont — reverse proxy (Node.js, Python, Go, etc.)
  • fastcgi_cache : met en cache la réponse FastCGI — PHP-FPM

Choisissez selon la stack. PHP → fastcgi_cache ; Node, Python, Go → proxy_cache. La logique est quasi la même ; j’illustre avec proxy_cache.

2.2 Configuration complète de proxy_cache

D’abord, définir 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 ligne par ligne :

  • levels=1:2 : arborescence à deux niveaux, évite trop de fichiers dans un seul dossier
  • keys_zone=my_cache:10m : nom de zone et mémoire pour les métadonnées ; 10m ≈ 80 000 clés
  • max_size=10g : taille max du cache ; au-delà, éviction LRU
  • inactive=60m : entrées non consultées depuis 60 min sont purgées
  • use_temp_path=off : écriture directe dans le cache, sans fichier temporaire intermédiaire

Puis activation dans server ou location :

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_cache my_cache;

        # Durées de validité
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_valid any 1m;

        # Clé de cache
        proxy_cache_key $scheme$request_method$host$request_uri;

        # Dégradation (voir plus bas)
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

        # En-tête de debug
        add_header X-Cache-Status $upstream_cache_status;
    }
}

proxy_cache_valid fixe la durée par code HTTP :

  • 200 302 10m : réponses OK mises en cache 10 minutes
  • 404 1m : 404 en cache 1 minute — limite les abus sur le backend
  • any 1m : autres codes, 1 minute

2.3 Clé de cache et invalidation

proxy_cache_key définit ce qui compte comme « même » requête. Par défaut $scheme$proxy_host$request_uri ; je recommande d’expliciter :

proxy_cache_key $scheme$request_method$host$request_uri;

Protocole, méthode, hôte et URI complets — plus précis si GET/POST coexistent ou plusieurs domaines.

Invalidation : souvent pénible. Stratégies courantes :

  1. Expiration temporelle : proxy_cache_valid
  2. Contournement actif : proxy_cache_bypass
  3. Purge : Nginx Plus avec proxy_cache_purge

J’utilise souvent le contournement via en-tête :

# Contournement via en-tête
proxy_cache_bypass $http_x_nocache;

# Ou via paramètre
proxy_cache_bypass $arg_nocache;

Pour rafraîchir : ?nocache=1 ou en-tête X-Nocache: 1.

2.4 Dégradation : servir même si le backend tombe

proxy_cache_use_stale est très utile : en erreur ou timeout backend, Nginx peut renvoyer une entrée expirée au lieu d’une erreur brute.

proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

L’an dernier, pendant le 11.11, un problème de scale backend provoquait des 502 intermittents. Grâce à cette dégradation, l’accès utilisateur a peu souffert — contenu un peu daté mais disponible. Au retour du backend, le cache s’est mis à jour.

Comparaison mesurée :

IndicateurSans cacheCache HITMode dégradé
Temps de réponse150-200 ms5-10 ms5-10 ms
QPS backend1000500
ExpérienceNormaleNormaleLégèrement plus lent
95 %
Taux de hits cache
Source: Mesures production

En hit cache, le temps passe de 200 ms à 5-10 ms — environ vingt fois plus rapide. Le gain est immédiat.

Chapitre 3 : pool de connexions — indispensable en haute concurrence

gzip et cache accélèrent le transfert ; le pool de connexions augmente la capacité. Par défaut, un worker Nginx plafonne à 1024 connexions — insuffisant dès que le trafic monte.

3.1 worker_connections : calculer le plafond

Formule :

Concurrence max = worker_processes × worker_connections

Serveur 8 cœurs, worker_processes à 8 (ou auto), worker_connections à 4096 :

Concurrence max = 8 × 4096 = 32768

Chiffre élevé, mais une requête utilise souvent deux connexions (client → Nginx, Nginx → backend). La concurrence utile est environ la moitié.

Configuration dans le bloc events :

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

use epoll est le défaut sous Linux ; l’écrire explicite la doc. multi_accept on accepte plusieurs connexions d’un coup — moins de file d’attente en pic.

3.2 keepalive client : réutiliser les connexions

Établir une connexion TCP coûte cher (three-way handshake). keepalive réutilise le lien client ↔ Nginx.

http {
    keepalive_timeout 65;
    keepalive_requests 1000;
}

keepalive_timeout 65 : connexion maintenue 65 s. Trop long → ressources occupées ; trop court → peu de réutilisation. 60-75 s est un bon intervalle.

keepalive_requests 1000 : max 1000 requêtes par connexion. Trop bas → coupures fréquentes ; trop haut → risque de fuite. 1000 est un bon compromis en pratique.

3.3 upstream keepalive : pool côté backend

Peu connu, très efficace : Nginx peut aussi réutiliser les connexions vers le backend.

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 de 64 connexions idle — à ajuster (souvent 4-8× le nombre de serveurs backend).

proxy_http_version 1.1 et proxy_set_header Connection "" sont obligatoires. Sans eux, upstream keepalive ne fonctionne pas.

Comparaison mesurée :

ConfigConnexions/minCharge CPUScénario
Sans upstream keepalive6000ÉlevéeFaible trafic
keepalive 323000MoyenneTrafic moyen
keepalive 641500FaibleFort trafic

upstream keepalive divise environ par deux le coût d’établissement des connexions — crucial en haute concurrence.

3.4 Tableau de paramètres recommandés

Réglages de départ par scénario :

ParamètreFaible trafic (<1000 QPS)Moyen (1000-5000 QPS)Élevé (>5000 QPS)
worker_processesautoautoauto
worker_connections102420484096
keepalive_timeout606575
keepalive_requests1005001000
upstream keepalive163264

Point de départ seulement — validez au bench. J’utilise wrk ou ab, courbes connexions / latence, puis j’affine.

Chapitre 4 : modèle de configuration — prêt pour la production

Les trois chapitres précédents posent le fond ; voici un template intégré. Adaptez les valeurs, la structure reste générique.

# nginx.conf — modèle production

user nginx;
worker_processes auto;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # 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;
    gzip_disable "msie6";

    # Cache
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;

    # Connexions client
    keepalive_timeout 65;
    keepalive_requests 1000;

    # Upstream
    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;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";

            # Cache
            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;

            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

Trois profils métier

E-commerce : accueil et fiches produit changent souvent — cache 10-15 min. upstream keepalive élevé : requêtes lourdes côté base.

API : fraîcheur des données — proxy_cache_valid souvent 1-5 min. gzip très utile sur JSON.

Site statique : HTML/CSS/JS stables — cache 1 h ou plus. gzip maximal sur le texte.

Chapitre 5 : problèmes fréquents et dépannage

Quelques cas que j’ai souvent vus, avec la marche à suivre.

Q : gzip inactif, pas de Content-Encoding: gzip

Vérifier :

  1. gzip on dans le bon bloc (http)
  2. gzip_types inclut le MIME de la réponse
  3. Taille ≥ gzip_min_length

Test : curl -H "Accept-Encoding: gzip" -I http://your-site.com

Q : faible taux de hits, X-Cache-Status surtout MISS

Causes fréquentes :

  • Clé de cache trop variable — chaque requête « unique »
  • proxy_cache_valid trop court
  • Backend avec Cache-Control: no-cache ou Set-Cookie

Inspectez les en-têtes de réponse.

Q : worker_connections insuffisant, erreurs 502

Dans les logs : worker_connections are not enough → concurrence au-delà du plafond.

Actions :

  1. Augmenter worker_connections
  2. Chercher une fuite de connexions (keepalive mal réglé)
  3. Ajouter des nœuds et équilibrer la charge

Q : mémoire haute, OOM fréquent

Pistes :

  • keys_zone de proxy_cache_path trop grand
  • Trop de fichiers cache, mmap lourd
  • Pool keepalive trop large

Réduisez ces paramètres ou ajoutez de la RAM.

Pour conclure

Les trois piliers du tuning Nginx : gzip, cache, pool de connexions. Bien combinés, ils peuvent doubler la vitesse perçue et multiplier la concurrence par trois ou quatre.

Le tuning n’est pas un one-shot. Ordre que je recommande :

  1. gzip d’abord : changement minimal, gain immédiat — dix minutes
  2. cache ensuite : stratégie selon le métier — une journée
  3. pool en dernier : bench requis — quand le trafic est stable

Après chaque changement, bench (wrk, ab) : latence, QPS, taux d’erreur. Pas au feeling — aux chiffres.

Checklist finale :

  • gzip activé, types MIME complets
  • gzip_comp_level entre 4 et 6
  • proxy_cache_path configuré, taille cohérente
  • proxy_cache_valid adapté au métier
  • proxy_cache_use_stale pour la dégradation
  • worker_connections ≥ 4096
  • keepalive_timeout 60-75 s
  • upstream keepalive + HTTP/1.1 + en-tête Connection
  • X-Cache-Status pour le debug

Voilà l’essentiel. Questions en commentaire — j’essaierai de répondre.

Processus de réglage des performances Nginx

Trois étapes pour configurer gzip, cache et pool de connexions en production

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Activer la compression gzip

    Ajoutez dans le bloc http :

    • gzip on; — activer la compression
    • gzip_vary on; — ajouter l'en-tête Vary
    • gzip_comp_level 6; — niveau de compression (4-6 recommandé)
    • gzip_min_length 1000; — ne pas compresser sous 1 Ko
    • gzip_types — types MIME : text/plain text/css application/json application/javascript
  2. 2

    Step 2: Configurer le cache proxy_cache

    En deux étapes :

    Étape 1 — définir le chemin de cache
    • proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m

    Étape 2 — activer le cache dans location
    • proxy_cache my_cache;
    • proxy_cache_valid 200 10m; — cache 10 min pour les réponses 200
    • proxy_cache_use_stale error timeout http_502; — stratégie de dégradation
  3. 3

    Step 3: Optimiser les paramètres du pool de connexions

    Trois réglages clés :

    • worker_connections 4096; — dans le bloc events
    • keepalive_timeout 65; keepalive_requests 1000; — dans le bloc http
    • upstream keepalive 64; proxy_http_version 1.1; proxy_set_header Connection ""; — pool de connexions backend

FAQ

Quel niveau de compression gzip choisir ?
Niveaux 4 à 6 recommandés. Niveau 4 : 72 % de compression, faible charge CPU — adapté si le CPU est limité. Niveau 6 : 75 % de compression, plus d'économie de bande passante — adapté si la bande passante est le goulot. Niveau 9 : charge CPU doublée pour un gain marginal, déconseillé.
proxy_cache ou fastcgi_cache : lequel choisir ?
Selon la stack backend : Node.js, Python, Go → proxy_cache ; PHP-FPM → fastcgi_cache. La logique de configuration est quasi identique : chemin de cache, durée de validité, clé de cache.
Quelle valeur pour worker_connections ?
Selon les cœurs CPU et le trafic estimé : faible trafic (&lt;1000 QPS) → 1024 ; moyen (1000-5000 QPS) → 2048 ; élevé (&gt;5000 QPS) → 4096. Concurrence réelle ≈ worker_processes × worker_connections ÷ 2.
Comment diagnostiquer un faible taux de hits du cache ?
Trois vérifications : 1. La clé de cache est-elle cohérente (éviter une clé unique par requête) ? 2. proxy_cache_valid est-il trop court ? 3. Le backend renvoie-t-il Cache-Control: no-cache ou Set-Cookie qui bloquent le cache ?
Pourquoi upstream keepalive exige HTTP/1.1 ?
HTTP/1.0 ne supporte pas keepalive par défaut ; HTTP/1.1 est requis pour réutiliser les connexions backend. Il faut aussi proxy_set_header Connection "" pour vider l'en-tête Connection, sinon Nginx ferme la connexion et keepalive ne fonctionne pas.
Comment ajuster la durée de cache selon le métier ?
E-commerce (accueil, fiches produit) : 10-15 min. API temps réel : 1-5 min. Site statique (HTML/CSS/JS) : 1 h et plus. Principe : plus le contenu change vite, plus le cache doit être court.

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

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog