Optimisation de la configuration Nginx pour WordPress

Tutoriel pour configurer Nginx de manière optimale avec WordPress.

Introduction

L'hébergement d'un site web WordPress n'est pas différent d'un site web standard en PHP et MySQL.

Une installation LEMP traditionnelle fonctionnera pour l'hébergement de WordPress. Par contre, elle ne fonctionnera pas optimale et ne sera certainement pas en mesure de gérer une quantité importante de trafic. L'utilisation de la mise en cache permet d'optimiser les performances.

Voici 2 manières de mise en cache qui peuvent être utilisées pour la mise en cache :

  • Mise en cache des objets
  • Mise en cache des pages

Mise en cache des objets

Comme avec la plupart des systèmes de gestion de contenu basés sur une base de données, WordPress s'appuie fortement sur la base de données. Une installation propre de WordPress 5.0 avec le thème Twenty Nineteen activé exécutera un total de 19 requêtes SQL sur la page d'accueil. Ensuite viendront les plug-ins tiers qui ajouteront à leur tour. Il n'est pas rare que plus de 100 requêtes SQL soient exécutées à chaque chargement de page, ce qui peut être une énorme pression sur le serveur.

Pour aider, WordPress a introduit une mise en cache d'objets interne qui stocke les données dans la mémoire PHP (y compris les résultats des requêtes de base de données). Cependant, le cache d'objets n'est pas persistant par défaut, ce qui signifie que la mise en cache est régénéré à chaque chargement de page, ce qui est extrêmement inefficace. Heureusement, WordPress peut utiliser une banque de données en mémoire externe telle que Redis. Cela peut réduire considérablement le nombre de requêtes de base de données à chaque chargement de page.

Sur Ubuntu, Redis peut être installé comme suit:

sudo apt-get update
sudo apt-get install -y redis-server

Une fois Redis installé, vous devrez installer le plug-in Redis Object Cache WordPress. La cache d'objets sera alors stockée dans Redis.

Bien qu'il s'agisse d'une énorme amélioration, cela ne supprime pas complètement la dépendance à la base de données, qui est souvent le plus gros goulot d'étranglement dans WordPress. En effet, les requêtes SQL pour créer l'index de publication seront toujours exécutées, car les résultats ne sont pas mis en cache. Cela nous amène à la mise en cache des pages...

Mise en cache des pages

Une mise en cache des pages est de loin la chose la plus importante que vous pouvez implémenter pour vous assurer que WordPress ne tombe pas sous la charge. Cela améliorera également le temps de réponse pour les demandes de page individuelles. Bien qu'il existe un certain nombre de plug-ins de cache WordPress disponibles, la plupart ne fonctionneront pas aussi bien qu'une solution côté serveur (car la demande est toujours gérée par PHP). Comme nous utilisons déjà LEMP, nous pouvons utiliser la mise en cache Nginx FastCGI, qui mettra en cache une version HTML statique de chaque page. Les visites suivantes serviront la version HTML statique sans jamais exécuter PHP ou MySQL.

Pour activer la mise en cache Nginx FastCGI, vous devrez ajuster quelques directives à votre configuration. Les éléments suivants doivent être ajoutés en haut de chaque fichier hôte virtuel avant le bloc serveur:

fastcgi_cache_path /path/to/cache/directory levels=1:2 keys_zone=FASTCGI-CACHENAME:100m inactive=60m;

Cela crée la zone de cache dans un dossier pour stocker les pages mises en cache. Avant que la requête soit envoyée à PHP, il est possible que Nginx vérifie d'abord qu'une clé de cache existe pour la requête en cours :

location ~ \.php$ {
    fastcgi_cache FASTCGI-CACHENAME;
    fastcgi_cache_valid 200 60m;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;

    fastcgi_pass  unix:/run/php/php7.3-fpm.sock;
    ...
}

La variable skip_cache vous permet de contourner le cache. Une stratégie typique consiste à ne mettre en cache que les demandes GET pour les utilisateurs non connectés. Il est également courant d'ignorer les demandes contenant une chaîne de requête:

# Ne pas ignorer le cache par défaut
set $skip_cache 0;

# Les requêtes POST doivent toujours être exécutées
if ($request_method = POST) {
    set $skip_cache 1;
}

# Les URLs contenant des chaînes de requête doivent toujours aller en PHP
if ($query_string != "") {
    set $skip_cache 1;
}

# Ne pas mettre en cache les URLs contenant les segments suivants
if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
    set $skip_cache 1;
}

# N'utilisez pas le cache pour les utilisateurs connectés ou les commentateurs récents
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|edd_items_in_cart|woocommerce_items_in_cart") {
    set $skip_cache 1;
}

La dernière pièce du puzzle de mise en cache consiste à s'assurer que Nginx peut mettre en cache les demandes qui ont des en-têtes Cache-Control et Set-Cookie. Si WordPress utilise l'un de ces en-têtes, Nginx pourrait ne pas être en mesure de mettre en cache la demande afin que nous puissions dire à Nginx de les ignorer. Nous devons également définir la clé de cache et indiquer à Nginx comment gérer les entrées mises en cache qui ont expiré:

fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout updating invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

Une configuration de base de Nginx pourrait ressembler à ceci:

fastcgi_cache_path /sites/example.com/cache levels=1:2 keys_zone=example.com:100m inactive=60m;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com;

    root /sites/example.com/public;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    index index.php;

    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache_use_stale error timeout updating invalid_header http_500;
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
    add_header Fastcgi-Cache $upstream_cache_status;

    # Ne pas ignorer le cache par défaut
    set $skip_cache 0;

    # Les requêtes POST doivent toujours être exécutées
    if ($request_method = POST) {
        set $skip_cache 1;
    }

    # Les URLs contenant des chaînes de requête doivent toujours aller en PHP
    if ($query_string != "") {
        set $skip_cache 1;
    }

    # Ne pas mettre en cache les URLs contenant les segments suivants
    if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
        set $skip_cache 1;
    }

    # N'utilisez pas le cache pour les utilisateurs connectés ou les commentateurs récents
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|edd_items_in_cart|woocommerce_items_in_cart") {
        set $skip_cache 1;
    }

    location ~ \.php$ {
        fastcgi_cache example.com;
        fastcgi_cache_valid 200 60m;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
        fastcgi_cache_methods GET HEAD;

        fastcgi_pass unix:/run/php/php7.3-fpm.sock;
        fastcgi_read_timeout 300;
        fastcgi_index index.php;
        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;

        # Mitigate https://httpoxy.org/ vulnerabilities
        fastcgi_param HTTP_PROXY "";
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

}

Benchmarks

Comparons quelques solutions de mise en cache courantes pour voir comment elles se mesurent. Je vais d'abord tester WordPress sans mise en cache, puis un plug-in de cache WordPress (Simple Cache), suivi de Nginx. Je vais également inclure Varnish pour démontrer que vous n'avez pas besoin d'un logiciel serveur supplémentaire pour effectuer une mise en cache simple.

Utilisons ApacheBench pour simuler 10 000 requêtes, avec une concurrence de 100 requêtes. Cela signifie que ApacheBench enverra un total de 10 000 demandes par lots de 100 à la fois.

ab -n 10000 -c 100 https://siteunderload.com/

Pour la version non mise en cache, j'utiliserai une concurrence de 20 demandes, car WordPress ne pourra jamais gérer cette quantité de concurrence sans mise en cache.

ab -n 10000 -c 20 https://siteunderload.com/

Source: SpinupWP

Comme prévu, WordPress ne fonctionne pas bien car PHP et MySQL sont impliqués dans le traitement de la demande. Le simple fait de retirer le serveur de base de données de l'équation en activant un plugin de cache de page donne une augmentation de 10 fois le nombre de requêtes. Cela va encore plus loin en utilisant une solution de mise en cache côté serveur.

Source: SpinupWP

WordPress une fois de plus ne fonctionne pas bien en raison du nombre de pièces mobiles nécessaires au traitement de la demande (Nginx, PHP et MySQL). En règle générale, moins vous avez de pièces mobiles dans le cycle de vie d'une demande, plus le temps de réponse moyen sera faible. C'est pourquoi la mise en cache Nginx FastCGI fonctionne si bien car elle doit servir qu'un fichier statique à partir du disque (qui sera probablement mis en cache en mémoire en raison du cache de page Linux).

Conclusion

Comme vous pouvez le voir sur les benchmarks, WordPress ne fonctionne pas bien sous charge. Cependant, avec quelques modifications simples à votre pile LEMP, vous pouvez améliorer considérablement les performances.

Besoin d'aide ?

Vous ne trouvez pas ce que vous cherchez ?

Parler avec un de nos spécialistes aujourd'hui.

Par courriel

Autres suggestions

Utiliser Let's Encrypt avec Nginx

Nginx

Tutoriel pour configurer un certificat SSL Let's Encrypt avec Nginx.

Rediriger HTTP à HTTPS avec Nginx

Nginx

Tutoriel pour configurer un redirection des requêtes HTTP vers HTTPS dans Nginx.

Validation de l'intégrité des fichiers WordPress

PHP WordPress

Utilisation d'un script créer par webO3 pour valider l'intégrité du code source d'un WordPress.