Blog

Configuration du cache de FastCGI sur nginx pour WordPress

Écrit le 10 11 2016 par Kévin MET _

Le petit tutoriel du jour concerne la façon de mettre en place le cache de FastCGI sur nginx et sa gestion via un plugin WordPress. Si vous n'utilisez pas Wordpress vous pouvez tout de même utiliser ce type de cache mais il faudra l'adapter à votre situation, c'est à dire trouver un plugin compatible avec votre CMS ou passer par l'écriture de votre propre gestionnaire de cache. Nous verrons cela dans la dernière partie.

Introduction

Pour commencer, nous allons voir à quoi sert ce cache et quels bénéfices et désavantages il apporte lorsqu'on l'active. Il permet donc de conserver en cache les résultats de l'appel à votre backend FastCGI qui bien souvent se trouve être php-fpm. Celui-ci est stocké d'une façon bien particulière que nous verrons dans la partie configuration. Cela permet d'accèlerer grandement le temps de chargement d'une page car on évite de passer par FastCGI et on va, à la place, chercher un fichier statique sur le disque dur. Dans ce tutoriel nous allons même configurer un montage tmps pour stocker ce cache en RAM et gagner ainsi encore un peu plus en performance. Les désavantages de cette solution sont similaires à toutes les solutions de cache. Elle n'est pas compatible avec tous les applicatifs et il faut donc bien réfléchir à sa mise en place, voire mieux, en parler avec le dev pour savoir ce qui peut être mis en cache ou non. Par exemple, un forum ne peut pas bénéficier de ce genre de cache car les pages ont besoin d'être mise à jour à chaque chargement. Les formulaires de contact sont également dans cette situation lorsqu'ils sont appelés. Bref, il faut faire assez attention à ce que l'on fait avant de le mettre en place.

Configuration

On commence par éditer /etc/fstab pour y ajouter notre montage de /var/cache/nginx en tmpfs qui contiendra le cache FastCGI. Voilà la ligne à y ajouter :


tmpfs	/var/cache/nginx tmpfs	defaults,size=256M			0	0

Dans mon cas, j'ai mis 256M ce qui est surdimensionné pour mon site mais je préfère la jouer safe. C'est à adapter à votre situation, vous pouvez commencer par cette valeur et baisser ou augmenter en fonction de votre besoin. Pour faire une estimation, il faut s'imaginer que le cache est basé, entre autres, sur l'uri, donc vous aurez à stocker le nombre de pages appelées dans le temps de garde du cache (attention, différent du temps de validation). Exemple, si votre site à 1000 pages et que celles-ci pèsent en moyenne 500Ko une fois générées en html, que vous avez mis le cache inactif à 1 heure et que durant une heure vous constatez que 600 pages différentes sont appelées. Vous faites 600 x 500Ko = 300Mo et comme on est jamais assez prudent et qu'on a surement assez de RAM, on fait un bon vieux "fois deux" des familles sur cette valeur car on a pas envie d'être emmerder en pleine nuit donc on part sur 600Mo 😉

Il faut ensuite créer le dossier, lui attribuer les bons droits et le monter :


root@web0:~# mkdir /var/cache/nginx
root@web0:~# chown www-data:www-data /var/cache/nginx/
root@web0:~# mount /var/cache/nginx/

On vérfie ensuite que le montage est OK :


root@web0:~# mount
[...]
tmpfs on /var/cache/nginx type tmpfs (rw,relatime,size=262144k)

C'est bon, on va donc pouvoir passez à la configuration du vhost. Dans mon exemple, il s'agit du site https://mad-rabbit.com/ dont voici le fichier de conf complet. On va détailler en dessous les paramètres importants :


fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=madrabbit:256m inactive=30d max_size=1000m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

server {
	listen 443 ssl http2 default_server;
	server_name mad-rabbit.com www.mad-rabbit.com;
	root /home/madrabbit/www;
	index index.html index.php;
	access_log /var/log/nginx/mad-rabbit.com-ssl_access.log;
	error_log /var/log/nginx/mad-rabbit.com-ssl_error.log;
	ssl_certificate /etc/nginx/ssl/mad-rabbit.chain.pem;
	ssl_certificate_key /etc/nginx/ssl/mad-rabbit.key;
	ssl_trusted_certificate /etc/nginx/ssl/gandi-standardssl-2.chain.pem;
	include /etc/nginx/mad-rabbit.com.rewrite;
	
	# Factcgi_cache activated by default. Will be disabled if necessary
	set $skip_cache 0;
	
	# POST requests and urls with a query string should always go to PHP
	if ($request_method = POST) {
		set $skip_cache 1;
	}
	if ($query_string != "") {
		set $skip_cache 1;
	}
	
	# Don't cache uris containing the following segments
	if ($request_uri ~* "/wp-admin/|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml|random_player.php|related.php|player/*.php|/forums/|android-push-notifications.php") {
		set $skip_cache 1;
	}
	
	# Don't use the cache for logged in users or recent commenters
	if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
		set $skip_cache 1;
	}
	
	location / {
		try_files $uri $uri/ /index.php?$args;
	}
	
	location ~ \.php$ {
		fastcgi_cache_bypass $skip_cache;
		fastcgi_no_cache $skip_cache;
		fastcgi_cache madrabbit;
		fastcgi_cache_valid 1d;
		fastcgi_pass unix:/run/php/php7.0-fpm-mad-rabbit.com-ssl.sock;
		fastcgi_index index.php;
		include fastcgi_params;
	}

	location ~ /\. {
		deny all;
		access_log off;
		log_not_found off;
	}
}

Tout n'est pas forcément nécéssaire et il ne faut donc pas la copier aveuglément telle quelle. On va commencer par le bloc tout en haut qui se trouve en dehors du bloc server :


fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=madrabbit:256m inactive=30d max_size=256m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

Dans cette configuration, voici ce qu'il se passe :

  • le dossier de cache est /var/cache/nginx
  • levels=1:2 : cela définit la hiérarchie du cache
  • keys_zone=madrabbit:256m : il faut bien faire attention à spécifier une zone différente par vhost
  • inactive=30d : lorsqu'une ressource n'est pas accédée depuis plus de 30 jours elle est supprimée, vous pouvez également utiliser m pour minutes et h pour heure
  • max_size=256m : si on dépasse ce seuil les ressources les plus vieilles sont supprimées
  • "$scheme$request_method$host$request_uri" : la clé de cache, vous pouvez la simplifier pour éviter les doublons si vous avez plusieurs noms de domaine servant le même contenu ou si vous servez le contenu en http et en https par exemple. Si vous utilisez le plugin nginx helper vous devez conserver précisément cette clé.
  • fastcgi_cache_use_stale error timeout invalid_header http_500 : si fastCGI rencontre une erreur 500 il ira taper dans le cache même si il est invalide, c'est là l'interêt d'avoir un inactive assez haut si cela est possible dans votre situation. Vous pouvez donc relancer php-fpm oklm 😃
  • fastcgi_ignore_headers Cache-Control Expires Set-Cookie : si on n'utilise pas ça, les headers renvoyés par php vont définir la durée de validité du cache (fastcgi_cache_valid)

Maintenant, on va regarder ce qui se trouve dans le bloc server :


# Factcgi_cache activated by default. Will be disabled if necessary
set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
	set $skip_cache 1;
}
if ($query_string != "") {
	set $skip_cache 1;
}

# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml|random_player.php|related.php|player/*.php|/forums/|android-push-notifications.php") {
	set $skip_cache 1;
}

# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
	set $skip_cache 1;
}

En fait, c'est déjà bien commenté donc je vais être bref. Par défaut, on active le cache et en fonction de situation particulière comme le fait qu'une requête soit en POST ou que l'uri contienne /forums/ on le désactive. Bref, t'as compris, tu peux skip le cache sur un tas de critères car il suffit de passer $skip_cache à 1.

On va ensuite voir la partie dans le bloc location ~ \.php$ :

 
location ~ \.php$ {
	fastcgi_cache_bypass $skip_cache;
	fastcgi_no_cache $skip_cache;
	fastcgi_cache madrabbit;
	fastcgi_cache_valid 1d;
	fastcgi_pass unix:/run/php/php7.0-fpm-mad-rabbit.com-ssl.sock;
	fastcgi_index index.php;
	include fastcgi_params;
}

On va voir les paramètres concernant le cache, pour les autres, go RTFM !

  • fastcgi_no_cache $skip_cache : permet de savoir si on va utiliser le cache ou non
  • fastcgi_cache madrabbit : il faut bien faire attention d'utiliser ce qu'on a définit dans la keys_zone
  • fastcgi_cache_valid 1d : si on a du cache qui a moins d'un jour on va l'utiliser, sinon on met à jour le cache ou on le crée

Il ne reste plus qu'à reload nginx en ayant au préalable vérifier que la configuration était OK :


root@web0:~# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@web0:~# service nginx reload

Si tout se passe bien, vous derviez avoir des dossiers dans /var/cache/nginx :


root@web0:~# ls -R /var/cache/nginx/
/var/cache/nginx/:
7

/var/cache/nginx/7:
d2

/var/cache/nginx/7/d2:
5b0f21f11c36615b7fa263057c19bd27

Il faut bien penser à surveiller cette partition dans nagios pour éviter qu'elle ne sature.

Purge du cache

Maintenant que ça fonctionne, on va voir comment purger ce cache. Pour commencer, il y a la méthode radicale et rapide :


root@web0:~# rm -rf /var/cache/nginx/*

Il n'y a rien de plus à faire et nginx commencera à recréer le cache tout seul sans qu'on est besoin de le relancer. Il faut également savoir que le fait de relancer nginx ne supprime pas le cache et ne l'invalide pas non plus. Pour que le cache soit vidé, il faut qu'il le soit physiquement sur la partition de cache.

Dans le cas de l'utilisation de WordPress il existe un plugin très pratique : nginx helper qui permet de gérer la purge du cache sur n'importe quelle version de nginx. Pour cela, il faut installer le plugin et ajouter dans wp-config.php cette variable :


/** Nginx fastcgi_cache_path */
define('RT_WP_NGINX_HELPER_CACHE_PATH','/var/cache/nginx');

Il faut également utiliser la clé précise fastcgi_cache_key "$scheme$request_method$host$request_uri";. qui sera utilisé pour calculer le hash md5 et donc fournir le chemin du fichier de cache. Ensuite, il faut aller dans les settings du plugin et cocher la case Delete local server cache files dans l'option Purge Method. Cela vous permet de ne purger que les pages qui ont changé lors de la modification du contenu.

Il existe bien d'autres méthodes pour gérer le cache à l'intégrer dans votre applicatif avec php ou d'autres langages mais je vous laisse faire les recherches sur google...

Log

Il est possible de logguer les résultats du cache de FastCGI en utilisant la variable $upstream_cache_status dans votre log_format. Par exemple si je définis ce log format :


log_format vhost_cache '$remote_addr - [$time_local] $upstream_cache_status "$request" $status';

Cela va produire ce genre de log :


188.165.6.38 - [10/Nov/2016:00:01:01 +0100] - "POST /wp-cron.php?doing_wp_cron=1478732460.1202449798583984375000 HTTP/1.1" 499
123.125.71.101 - [10/Nov/2016:00:01:01 +0100] BYPASS "GET /feed/ HTTP/1.1" 200
66.249.64.123 - [10/Nov/2016:00:01:22 +0100] EXPIRED "GET /manga-baseball/ HTTP/1.1" 200
188.165.6.38 - [10/Nov/2016:00:01:43 +0100] HIT "GET / HTTP/1.1" 200

Il est donc possible de compter, voire grapher (cela fera l'objet d'un autre article) les statistiques du cache et d'affiner les réglages pour avoir un maximum de HIT.

EDIT du 11/11/2016: Une fois que vous avez fait un peu chauffer votre cache vous pouvez utiliser cette commande pour connaitre la taille moyenne des fichiers dans votre cache pour en affiner les réglages :


root@web0:~# find /var/cache/nginx/ -type f -exec du {} \; | awk '{ sum += $1; n++ } END { if (n > 0) print "Taille moyenne: " sum / n"Ko"; }'
Taille moyenne: 25.8307Ko

C'est vraiment un truc grossier donc à prendre avec des pincettes mais ça peut mettre sur une piste quand on sait vraiment pas ce qu'on héberge (et dans ce cas, il est recommandé de ne pas mettre ce type de cache en place...)

Sources :

♥ Partage sur tes réseaux sociaux ♥
Kévin MET
Kévin MET

Auteur de ce blog et gérant de la société MNT-TECH, je publie sur ce blog lorsque le temps me le permet et lorsqu'un sujet qui me parait intéressant n'a pas encore été abordé en français. Toutes les informations techniques présentes sur cette page peuvent être réutilisées moyennant le fait de citer la source.