Tuner Nginx pour la haute charge

Tuner Nginx pour la haute charge

Configurez Nginx pour servir des milliers de connexions simultanées. Ce guide couvre les workers, le keepalive, la compression gzip/brotli, le cache et les optimisations TLS/HTTP2 pour un VPS de production.

Introduction

Nginx par défaut sert correctement quelques centaines de requêtes par seconde. Avec un tuning correct sur un VPS moderne, il dépasse facilement 10 000 req/s pour du contenu statique et 1000-3000 req/s pour du dynamique.

Ce tuto donne les paramètres essentiels pour :

  • Maximiser les connexions simultanées
  • Activer la compression (gzip + brotli)
  • Optimiser le cache fichier statique
  • Configurer TLS moderne (HTTP/2, OCSP stapling)
  • Réduire la latence

Prérequis

  • VPS Linux avec Nginx 1.18+
  • Accès root
  • Optimisations kernel déjà appliquées (voir /docs/article/kernel-tuning)

Étape 1 : Identifier votre Nginx

nginx -V 2>&1

Notez les modules compilés (gzip, brotli, http2, etc.). Si brotli manque sur Debian/Ubuntu, installez libnginx-mod-brotli :

sudo apt install -y libnginx-mod-brotli

Étape 2 : Configurer les workers

Le worker est le process qui sert les requêtes. Sur un VPS multi-coeurs, configurez :

sudo nano /etc/nginx/nginx.conf

Bloc principal :

# Nombre de workers = nombre de CPUs
worker_processes auto;

# Augmenter les file descriptors par worker
worker_rlimit_nofile 65535;

events {
    # Connexions simultanées par worker (worker_processes × worker_connections = max théorique)
    worker_connections 16384;
    
    # Multi-accept (accepte plusieurs connexions par event)
    multi_accept on;
    
    # epoll est le meilleur sur Linux
    use epoll;
}

Avec worker_processes auto et 4 cores : 4 workers × 16384 = 65536 connexions max simultanées.

Étape 3 : Optimisations HTTP globales

Dans le bloc http { } :

http {
    # === Performances de base ===
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # === Keepalive (réutilisation des connexions TCP) ===
    keepalive_timeout 30;
    keepalive_requests 1000;
    
    # === Hash tables pour résoudre les serveurs plus vite ===
    server_names_hash_bucket_size 128;
    server_names_hash_max_size 4096;
    types_hash_max_size 2048;
    
    # === Tampons (buffers) ===
    client_body_buffer_size 16K;
    client_header_buffer_size 1k;
    client_max_body_size 100M;       # Uploads
    large_client_header_buffers 4 16k;
    
    # === Timeouts ===
    client_body_timeout 12;
    client_header_timeout 12;
    send_timeout 10;
    reset_timedout_connection on;
    
    # === Hide version Nginx (sécurité) ===
    server_tokens off;
}

Étape 4 : Activer la compression gzip

http {
    # === Compression gzip ===
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/ld+json
        application/manifest+json
        application/rss+xml
        application/vnd.geo+json
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/bmp
        image/svg+xml
        image/x-icon
        text/cache-manifest
        text/css
        text/plain
        text/vcard
        text/vnd.rim.location.xloc
        text/vtt
        text/x-component
        text/x-cross-domain-policy
        text/xml;
}

gzip_comp_level 6 est l'équilibre optimal (1 = vite/grosses tailles, 9 = compact/lent).

Étape 5 : Activer brotli (si installé)

Brotli compresse 15-25% mieux que gzip pour le texte. Supporté par tous les navigateurs modernes.

http {
    # === Compression brotli ===
    brotli on;
    brotli_comp_level 6;
    brotli_min_length 1024;
    brotli_types
        application/javascript
        application/json
        application/xml
        application/atom+xml
        application/rss+xml
        image/svg+xml
        text/css
        text/javascript
        text/plain
        text/xml;
}

Si vous voyez Content-Encoding: br dans les headers, brotli est actif.

Étape 6 : Configurer la mise en cache des fichiers statiques

http {
    # === Cache fichier (file descriptors) ===
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

Et dans vos server { } blocks, ajoutez les headers de cache pour les statiques :

server {
    # ...
    
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    location ~* \.(pdf|zip|tar|gz)$ {
        expires 7d;
        add_header Cache-Control "public";
    }
}

Étape 7 : Optimisations TLS / HTTPS

http {
    # === SSL ===
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;  # En TLS 1.3, les clients choisissent
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    
    # === Session SSL ===
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 4h;
    ssl_session_tickets off;
    
    # === OCSP stapling (vérifie le cert sans round-trip client) ===
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=60s;
    resolver_timeout 5s;
    
    # === HSTS ===
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Étape 8 : Activer HTTP/2 et HTTP/3

Dans chaque server { listen 443 ... } :

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    
    # HTTP/3 si Nginx 1.25+ avec module quic
    # listen 443 quic reuseport;
    # add_header Alt-Svc 'h3=":443"; ma=86400';
    
    # ...
}

Vérifiez avec :

curl -I --http2 https://votre-domaine.com

Vous devez voir HTTP/2 200.

Étape 9 : Reverse proxy vers une app backend (PHP-FPM, Node, etc.)

Tuning spécifique pour réduire la latence :

upstream backend {
    server 127.0.0.1:3000;
    
    # Keepalive vers le backend (très important !)
    keepalive 32;
    keepalive_timeout 60s;
    keepalive_requests 10000;
}

server {
    location / {
        proxy_pass http://backend;
        
        # Forcer HTTP/1.1 pour le keepalive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Headers à transmettre
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Buffer la réponse pour ne pas bloquer le backend
        proxy_buffering on;
        proxy_buffer_size 16k;
        proxy_buffers 32 16k;
        proxy_busy_buffers_size 32k;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Étape 10 : Cache de proxy (très puissant pour les apps lentes)

http {
    # === Cache proxy ===
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:100m max_size=10g inactive=60m use_temp_path=off;
}

server {
    location / {
        proxy_cache app_cache;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 1;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_lock on;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
        
        # Ajouter un header pour debug
        add_header X-Cache-Status $upstream_cache_status;
        
        proxy_pass http://backend;
        # ... reste du config
    }
}

Test :

curl -I https://votre-domaine.com
# Cherchez "X-Cache-Status: HIT" après le 2ème call

Étape 11 : Rate limiting (anti-flood)

http {
    # Limite par IP
    limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
}

server {
    # Général : 30 req/s par IP, burst de 50
    limit_req zone=general burst=50 nodelay;
    
    location /login {
        # Login : 5 req/min par IP (anti brute-force)
        limit_req zone=login burst=2 nodelay;
        # ...
    }
}

Étape 12 : Tester la config et appliquer

sudo nginx -t
sudo systemctl reload nginx

Étape 13 : Benchmarker

# Installer wrk
sudo apt install -y wrk

# Test
wrk -t12 -c400 -d30s https://votre-domaine.com

Sortie type :

Running 30s test @ https://votre-domaine.com
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max
    Latency    45ms     20ms    300ms
    Req/Sec     1.5k    200      2k
  Requests/sec:  17500
  Transfer/sec:   30MB

Dépannage

"worker_connections exceeded"

Augmentez worker_connections ou worker_rlimit_nofile.

Erreur "Too many open files"

Le service ne respecte pas les limites système. Ajoutez à /etc/systemd/system/nginx.service.d/override.conf :

[Service]
LimitNOFILE=65535
sudo systemctl daemon-reload
sudo systemctl restart nginx

Latence élevée pour le PHP

Souvent dû à un PHP-FPM mal tuné. Augmentez pm.max_children dans le pool PHP-FPM :

sudo nano /etc/php/8.3/fpm/pool.d/www.conf
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20

Cache ne fonctionne pas

Vérifiez les permissions :

sudo chown -R www-data:www-data /var/cache/nginx

Et que vos requêtes ne contiennent pas de cookie (sinon le cache est skip par défaut).

Commandes utiles

# Tester la config
sudo nginx -t

# Recharger (sans coupure)
sudo systemctl reload nginx

# Voir les connexions actives
ss -tn | grep :443 | wc -l

# Top des IPs par requêtes (depuis access.log)
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# Status temps réel (nécessite module stub_status)
curl http://127.0.0.1/nginx_status

# Voir si gzip/brotli fonctionne
curl -I -H "Accept-Encoding: gzip, br" https://votre-domaine.com

# Profiling avec ngx_lua (si installé)
location /metrics {
    content_by_lua_block { ngx.say("hello") }
}

Conclusion

Avec ces optimisations, votre Nginx est prêt pour la haute charge :

  • HTTP/2 + keepalive + brotli → -40 à -60% bande passante client
  • Cache proxy → réduit la charge du backend de 90%+
  • Rate limiting → protection anti-flood applicative

Pour aller plus loin :

  • Compilez Nginx avec ngx_brotli et ngx_lua depuis les sources
  • Ajoutez Cloudflare devant pour offloader encore plus de cache
  • Configurez WAF ModSecurity pour la sécurité applicative

Ressources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members