Optimiser les performances de WordPress avec Varnish sur Infomaniak (Cloud Managé)

Lorsque l’on optimise son site web, outre la configuration du thème, le choix des extensions ou encore la compression des images, la configuration de l’hébergement est souvent négligée.

Différents systèmes de caches sont applicables et aujourd’hui, je vais vous expliquer comment j’ai pu mettre en place Varnish sur mon hébergement dédié (managé) chez Infomaniak.

Afin de gérer la purge, j’utiliserai WP Rocket mais vous pouvez tout à fait utiliser une extension tierce (par exemple. Proxy Cache Purge)

Qu’est-ce que Varnish ?

Sûrement un problème de génération, bercé à grands coups de pub, mais dès que je parle de Varnish à quelqu’un, on me sort une blague sur la lessive…

Varnish (avec un R donc) est un serveur de cache HTTP, souvent appelé Proxy Cache. Il vient en amont (en « frontal ») d’un serveur web (par exemple, Apache ou Nginx) et va mettre en cache les ressources statiques (fichiers HTML, CSS, JS, images) pour les délivrer très rapidement aux visiteurs. En effet, ce proxy va utiliser la mémoire vive et éviter les accès aux disques et les appels à Apache (qui n’est, d’ailleurs, pas le plus optimisé des serveurs web pour délivrer les fichiers statiques).

Particularité

Varnish ne supporte pas le protocole HTTPS, du moins, il n’est pas prévu pour gérer cet aspect. Cependant, c’est une volonté des développeurs qui estiment que Varnish n’a pas à faire cela.

Attention, cela ne veut pas dire que l’on ne peut pas l’utiliser pour un site en HTTPS et heureusement (en 2022, il me semble inconcevable de créer un site non « sécurisé » avec un avertissement dans le navigateur).

Ainsi, il va falloir le placer « derrière » un autre proxy qui lui, sera capable de délivrer un certificat et gérer les échanges sécurisés avec le client. Chez Infomaniak, sur les serveurs de l’offre Cloud Managé, c’est le logiciel HAProxy qui joue ce rôle.

Ainsi, voici ce qui va se passer lors que quelqu’un tentera d’accéder à un site internet sous WordPress (dans le cadre d’une configuration telle que j’ai l’habitude de mettre en place sur un hébergement Infomaniak managé((D’autres hébergeurs auront choisi une autre pile de technologies. Par exemple HAProxy, est un proxy qui ne semble pas adopté par beaucoup d’hébergeurs, on rencontre par exemple assez souvent Nginx en frontal))).

Avantages de Varnish

  • Économiser les ressources du serveur (processeur / lecture du disque)
  • Délivrer plus rapidement les fichiers aux clients (internautes, Google Bot, etc.)
  • Cache persistant si bien configuré

Inconvénients de Varnish

  • La configuration est parfois délicate :
    Il est préférable de faire beaucoup de tests avec les sites dynamiques, notamment e-commerce car Varnish ne doit absolument pas mettre en cache les partie dynamiques. Celles et ceux qui ont expérimenté Varnish sur une boutique en ligne, auront déjà probablement été confrontés au « panier partagé » : un visiteur met des éléments dans son panier et c’est un autre visiteur qui voit le contenu du panier de l’autre internaute… pas trop pratique pour e-commerce, vous en conviendrez !
  • Cache parfois « capricieux » à vider :
    En fait, il n’est pas « capricieux » en soit, mais il est nécessaire de s’assurer que l’ordre de rafraîchissement du cache (FLUSH) soit bien exécuté par Varnish. Souvent, cela se passe au niveau de votre applicatif (WordPress en l’occurence) qui doit envoyer la commande à Varnish au bon moment, généralement à la mise à jour d’un contenu existant. Ici, j’utiliserai le flush natif de WP Rocket, qui fonctionne très bien dans ma configuration.
    Pour WordPress, cette extension Proxy Cache Purge est souvent citée pour réaliser cette tâche.

Paramétrer Varnish sur serveur Cloud Managé Infomaniak

Avant de comencer : Afin de mettre en place Varnish sur vos sites, je vous recommande dans un premier temps, de faire cela sur un site dédié au test, si possible avec les mêmes extensions WordPress que celles que vous avez l’habitude d’employer.

Pour comprendre le fonctionnement global de Varnish et la structure des fichiers de configuration, je vous recommande la lecture de cet article sur le blog de LinkValue très utile.

Les étapes pour la mise en place

  1. Installer HAProxy et Varnish sur votre instance d’hébergement Infomaniak
  2. Préparer un fichier de configuration VCL pour le serveur Varnish
  3. Installer WP Rocket et le paramétrer
  4. Installer le fichier de configuration Varnish sur Infomaniak
  5. Activer Varnish pour le site en question
  6. Tester le bon fonctionnement du cache
  7. Tester le bon fonctionnement de la purge

1. Installer HAProxy & Varnish via le manager Infomaniak

Vous trouverez ces outils dans le menu « Fast Installer » de votre serveur cloud.

Installation de Varnish sur serveur cloud managé Informaniak

L’installation met en général entre 2 et 5 minutes et vous recevrez un mail de confirmation à la fin de chaque opération.

2. Configurer Varnish avec le fichier .vcl

Voici la documentation officielle de Varnish pour WordPress sur laquelle je me suis basé.

J’ai adapté le fichier de configuration pour le rendre compatible avec la version de Varnish fournie par Infomaniak et d’autre part, pour le faire coller à mes configurations WordPress « classiques » : sites vitrines principalement, mais aussi e-commerces (avec WooCommerce).

Voici le fichier à modifier, puis à copier-coller dans les outils de configuration de Varnish sur la manager d’Infomaniak.

#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.
#
# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/
# and http://varnish-cache.org/trac/wiki/VCLExamples for more examples.

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

import std;
import directors;

# Default backend definition. Set this to point to your content server.
backend default {
	.host = "127.0.0.80";
	.port = "80";
	.connect_timeout = 5s;
	.first_byte_timeout = 60s;
	.between_bytes_timeout = 60s;
}

acl local {
	"localhost";
	"127.0.0.1";
	"127.0.0.80";
	"::1";
	"195.15.xxx.xxx";
	"2001:1600:3:19:xxxx:xxxx:xxxx:xxxx";
	"193.248.xxx.xxx";
}

sub vcl_synth {

	if (resp.status == 750) {
		set resp.status = 301;
		set resp.http.Location = req.http.x-Redir-Url;
		return(deliver);
	}
	
}

sub vcl_recv {

    # Remove query string added automatically by Facebook and others services
	# these pages are identical even if the query string parameters are differents
	if (req.url ~ "(\?|&)(fbclid|gclid|utm_[a-z]+)=") {
		set req.url = regsuball(req.url, "(fbclid|gclid|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", "");
		set req.url = regsub(req.url, "[?|&]+$", "");
	}
	
	# Remove empty query string parameters
	# e.g.: www.example.com/index.html?
	if (req.url ~ "\?$") {
		set req.url = regsub(req.url, "\?$", "");
	}
	
	# Remove port number from host header
	set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
	
	# Sorts query string parameters alphabetically for cache normalization purposes
	set req.url = std.querysort(req.url);
	
	# Remove the proxy header to mitigate the httpoxy vulnerability
	# See https://httpoxy.org/
	unset req.http.proxy;
	
	  if (req.method == "PURGE" || req.method == "BAN") {
	  
	  # If the IP isn't a match, we can't flush cache.
	  if (!client.ip ~ local) {
		  return (synth(405, "This IP is not allowed to send PURGE requests."));
	  }
	  
	  # If we're trying to regex, then we need to use the ban calls:
	  if (req.http.X-Purge-Method == "regex") {
		  ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host ~ " + req.http.host);
		  return (synth(200, "Purged"));
	  }
	  
	  # Backported catch for exact cache flush calls:
	  if (req.http.X-Purge-Method == "exact") {
		  ban("obj.http.x-url == " + req.url + " && obj.http.X-Req-Host == " + req.http.host);
		  return (synth(200, "Purged"));
	  }
	  
	  # Otherwise, we strip the query params and flush as is:
	  set req.url = regsub(req.url, "\?.*$", "");
	  return (purge);
		
	  }
	else{
	
		if ( req.http.X-Forwarded-Proto !~ "(?i)https" ) {
			set req.http.x-Redir-Url = "https://" + req.http.host + req.url;
			return ( synth( 750 ));
		}
	}
	  
	# Only handle relevant HTTP request methods
	if (
		req.method != "GET" &&
		req.method != "HEAD" &&
		req.method != "PUT" &&
		req.method != "POST" &&
		req.method != "PATCH" &&
		req.method != "TRACE" &&
		req.method != "OPTIONS" &&
		req.method != "DELETE"
	) {
		return (pipe);
	}
	
	# Only cache GET and HEAD requests
	if (req.method != "GET" && req.method != "HEAD") {
		set req.http.X-Cacheable = "NO:REQUEST-METHOD";
		return(pass);
	}
	
	
	# Mark static files with the X-Static-File header, and remove any cookies
	# X-Static-File is also used in vcl_backend_response to identify static files
	if (req.url ~ "^[^?]*\.(7z|avi|avif|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|json|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
		set req.http.X-Static-File = "true";
		unset req.http.Cookie;
		return(hash);
	}
	
	# No caching of special URLs, logged in users and some plugins
	if (
		req.http.Cookie ~ "wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+|wordpress_logged_in_|comment_author|PHPSESSID|wp-resetpass-[a-zA-Z0-9]" ||
		req.http.Authorization ||
		req.url ~ "add_to_cart" ||
		req.url ~ "edd_action" ||
		req.url ~ "nocache" ||
		req.url ~ "no_cache" ||
		req.url ~ "^/addons" ||
		req.url ~ "^/bb-admin" ||
		req.url ~ "^/bb-login.php" ||
		req.url ~ "^/bb-reset-password.php" ||
		req.url ~ "^/cart" ||
		req.url ~ "^/checkout" ||
		req.url ~ "^/control.php" ||
		req.url ~ "^/login" ||
		req.url ~ "^/logout" ||
		req.url ~ "^/lost-password" ||
		req.url ~ "^/my-account" ||
		req.url ~ "^/product" ||
		req.url ~ "^/register" ||
		req.url ~ "^/register.php" ||
		req.url ~ "^/server-status" ||
		req.url ~ "^/signin" ||
		req.url ~ "^/signup" ||
		req.url ~ "^/stats" ||
		req.url ~ "^/wc-api" ||
		req.url ~ "^/wp-admin" ||
		req.url ~ "^/wp-comments-post.php" ||
		req.url ~ "^/wp-cron.php" ||
		req.url ~ "^/wp-login.php" ||
		req.url ~ "^/wp-activate.php" ||
		req.url ~ "^/wp-mail.php" ||
		req.url ~ "^/wp-login.php" ||
		req.url ~ "add-to-cart=" ||
		req.url ~ "wc-api=" ||
		req.url ~ "^/preview=" ||
		req.url ~ "preview=" ||
		req.url ~ "^/\.well-known/acme-challenge/"
	) {
		 set req.http.X-Cacheable = "NO:Logged in/Got Sessions";
		 if(req.http.X-Requested-With == "XMLHttpRequest") {
			 set req.http.X-Cacheable = "NO:Ajax";
		 }
		return(pass);
	}
	
	# Remove any cookies left
	unset req.http.Cookie;
	return(hash);
}



sub vcl_backend_response {
	# Happens after we have read the response headers from the backend.
	# 
	# Here you clean the response headers, removing silly Set-Cookie headers
	# and other mistakes your backend does.
	# Inject URL & Host header into the object for asynchronous banning purposes
	set beresp.http.x-url = bereq.url;
	set beresp.http.x-host = bereq.http.host;

	# Define grace
	# https://varnish-cache.org/docs/trunk/users-guide/vcl-grace.html
	# no keep - the grace should be enough for 304 candidates
	set beresp.grace = 4h;
	
	
	# cache only successfully responses
	# you can set 404 to be cached, but if your site generate temporarily 404 errors (for eg. when Wp Rocket Async CSS & Elementor are generating css file, it could be longer than expected and 404 should'not be must not be cached or it will remain inaccessible until the next flush)
	if (beresp.status != 200 && beresp.status != 410 && beresp.status != 301 && beresp.status != 302 && beresp.status != 304 && beresp.status != 307) {
		set beresp.http.X-Cacheable = "NO:UNCACHEABLE"; 
		set beresp.ttl = 10s;
		set beresp.uncacheable = true;
	}
	else
	{
	
		# If we dont get a Cache-Control header from the backend
		# we default to 1h cache for all objects
		if (!beresp.http.Cache-Control) {
			set beresp.ttl = 1h;
			set beresp.http.X-Cacheable = "YES:Forced";
		}
		
		
		# If the file is marked as static we cache it for 1 day
		if (bereq.http.X-Static-File == "true") {
			unset beresp.http.Set-Cookie;
			set beresp.http.X-Cacheable = "YES:Forced";
			set beresp.ttl = 1d;
		}
		
		# Remove the Set-Cookie header when a specific Wordfence cookie is set
		if (beresp.http.Set-Cookie ~ "wfvt_|wordfence_verifiedHuman") {
			unset beresp.http.Set-Cookie;
	 	}
		
		if (beresp.http.Set-Cookie) {
			set beresp.http.X-Cacheable = "NO:Got Cookies";
		} elseif(beresp.http.Cache-Control ~ "private") {
		
			if(beresp.http.Cache-Control ~ "public" && bereq.http.X-Static-File == "true" ) {
			set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private,", "");
			set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private", "");
			set beresp.http.X-Cacheable = "YES";
			}
			elseif(bereq.http.X-Static-File == "true" && (beresp.http.Content-type ~ "image\/webp" || beresp.http.Content-type ~ "image\/avif") )
			{
			set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private,", "");
			set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private", "");
			set beresp.http.X-Cacheable = "YES";
			}
			else{
			set beresp.http.X-Cacheable = "NO:Cache-Control=private";
			}
		}	
	
	}
	
	
}

sub vcl_deliver {
	# Happens when we have all the pieces we need, and are about to send the
	# response to the client.
	# 
	# You can do accounting or modifying the final object here.
	
	
	# Debug header
	if(req.http.X-Cacheable) {
		set resp.http.X-Cacheable = req.http.X-Cacheable;    
	} elseif(obj.uncacheable) {
		if(!resp.http.X-Cacheable) {
			set resp.http.X-Cacheable = "NO:UNCACHEABLE";        
		}
	} elseif(!resp.http.X-Cacheable) {
		set resp.http.X-Cacheable = "YES";
	}
	# End Debug Header
	
	if (resp.http.X-Varnish ~ "[0-9]+ +[0-9]+") {
		set resp.http.X-Cache = "HIT";
	  } else {
		set resp.http.X-Cache = "MISS";
    }
	
    #unset resp.http.X-Cacheable
	
	# Cleanup of headers
	unset resp.http.x-url;
	unset resp.http.x-host;
}Langage du code : Bash (bash)

Télécharger le fichier de configuration sur github

Les adresses IP

Le premier point à changer dans le script et que vous devez absolument personnaliser pour votre propre usage sont les adresses IP qui seront authorisées à lancer une commande de purge :

acl local {
    "localhost";
    "127.0.0.1";
    "127.0.0.80";
    "::1";
    "195.15.xxx.xxx";
    "2001:1600:3:19:xxxx:xxxx:xxxx:xxxx";
    "193.248.xxx.xxx";
}Langage du code : Bash (bash)

L’adresse IP 127.0.0.80 est l’adresse de HAproxy (et Infomaniak indique de bien la laisser dans la configuration).

C’est important pour que la purge fonctionne que vous rajoutiez les adresses de votre serveur1.

J’ai fait le choix d’y rajouter à la fin mon adresse IP fixe afin de pouvoir lancer une purge en ligne de commande depuis le terminal de mon Mac. C’est facultatif, mais cela peut être pratique, notamment pour déboguer :

Boucle de redirection HTTP / HTTPS

Attention, je pars du principe que le site est bien accessible en HTTPS et qu’il sera uniquement accessible aux visiteurs en HTTPS, cela a une importance sur les informations qui suivent.

Préambule

Chez Infomaniak (mais pas que), une bonne (et la meilleure à mon sens) façon de faire une redirection de :

http://reuhno.fr http://www.reuhno.fr et https://reuhno.fr ou encore http://reuhno.com et https://reuhno.com

vers https://www.reuhno.fr

est d’employer cette règle de redirection dans le fichier .htaccess, juste avant les règles par défaut de WordPress :

#BEGIN HTTPS / RENAUD
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www. [OR]
RewriteCond %{HTTP_HOST} !reuhno.fr [NC]
RewriteRule (.*) https://www.reuhno.fr/$1 [R=301,L]
# END  HTTPS / RENAUDLangage du code : Apache (apache)

Tout est expliqué sur cette documentation d’Informaniak et je l’ai juste ajusté pour prendre en compte les domaines secondaires définis en alias (reuhno.com dans mon exemple).

Le problème de boucle de redirection Varnish

Par défaut (et sans traiter ce cas comme je l’ai fait dans la configuration .VCL de Varnish), lorsque le cache est vide, si l’on fait un premier appel non sécurisé (par exemple http://www.reuhno.fr), Varnish tente de faire une redirection vers la version HTTPS qu’il stocke dans sa mémoire (pour faire court : gros plantage du site avec une boucle de redirection).

Pour une raison qui m’échappe encore un peu, cette boucle de redirection ne se fait que si l’on appelle en premier la version HTTP. Le genre de « bug » (appelons cela « comportement non documenté » pour le principe) qui m’a rendu fou !

Puisqu’il est impossible de savoir si le premier appel après une purge sera HTTP ou HTTPS, il nous faut impérativement traiter cela.

La solution que j’ai trouvé est de traiter la redirection vers HTTPS en cas d’appel HTTP grâce à l’instruction suivante dans la subroutine vcl_recv :

if ( req.http.X-Forwarded-Proto !~ "(?i)https" ) {
    set req.http.x-Redir-Url = "https://" + req.http.host + req.url;
    return ( synth( 750 ));
}Langage du code : Bash (bash)

Cette instruction fonctionne de pair avec la sous-routine (subroutine) vcl_synth :

sub vcl_synth {
    if (resp.status == 750) {
        set resp.status = 301;
        set resp.http.Location = req.http.x-Redir-Url;
        return(deliver);
    }
}Langage du code : Bash (bash)

Elle permet de gérer correctement la redirection en créant une règle et éviter ainsi une boucle de redirection un peu vicieuse.

Les URL à ne pas mettre en cache

Utilisateurs connectés et sessions

De nombreuses URL ou conditions doivent être indiquées pour ne pas perturber le fonctionnement de WordPress.

Hors de question de mettre en cache l’administration du site ou lorsqu’il y a un produit dans le panier.

Globalement, dès qu’il y a une session en cours pour l’utilisateur, on bypass le cache, c’est plus prudent. On perd ainsi le bénéfice de Varnish sur les ressources de types pages HTML, mais croyez-moi, c’est vraiment plus prudent.

Erreurs 503

Également, j’ai rajouté une condition pour ne mettre en cache que les fichiers qui répondent avec un code HTTP que je juge valide : 200, 301, 302, 307, 410, etc.

Sans cette condition particulière et avec le reste des conditions définie, Varnish stockait en cache toutes les requêtes, y compris les 503, ce qui est problématique si le serveur Apache rencontre momentanément une erreur. En effet, sans cette règle, tous les fichiers resteraient en erreur((Une erreur 503 peut vite arriver chez Infomaniak si le nombre de requêtes est trop important d’un coup, par exemple lors de l’utilisation d’un CDN, au premier chargement CloudFare demande beaucoup fichiers et la limitation d’apache rentre en action.. et nous voici avec de belles 503)) pour les visiteurs jusqu’au prochain flush de Varnish.

Également, je me suis rendu compte que mettre en cache les pages 404 était une fausse bonne idée. En effet, lors de l’utilisation de la fonctionnalité de concaténation de CSS de plugins de cache tels que WP Rocket ou bien avec la génération des styles d’Elementor, il arrivait de tomber sur une page 404 le temps de la génération du fichier (car ce n’est des fois pas instantané).

Si Varnish l’intercepte, alors elle restera en 404 jusqu’à la prochaine purge de Varnish (durée imprévisible car le cache WP Rocket peut se régénérer a tout moment de la journée ou de la nuit, ou bien le gestionnaire du site peut ne pas s’apercevoir de cette erreur).

Les fichiers statiques et le cas des WebP/AVIF

Les fichiers statiques sont de bons candidats à la mise en cache par Varnish. Par définition, ils sont statiques et donc ne vont pas être amenés à changer souvent.

WP Rocket ajoute par défaut l’entête Cache-Control: public sur tous les fichiers statiques.

En très résumé, cet entête indique que le fichier peut être mis en cache au niveau du navigateur visiteur mais également au niveau d’un CDN ou Proxy.

Cela tombe bien, Varnish est un proxy !

L’exception WebP/AVIF réécrits par .htaccess

J’ai pour habitude d’utiliser une extension pour compresser les images au format WebP et/ou AVIF. Il s’agit de WebP Converter for Media. Tout comme d’autres extensions du genre, son fonctionnement est astucieux.

Lors qu’un navigateur supporte le format WebP, ce dernier « s’annonce » en envoyant une requête Accept: image/webp au serveur Web.

Apache, grâce à une règle de réécriture (via .htaccess), transmet l’image au format WebP (si existante), sans changer l’URL du fichier, qui aux yeux de l’internaute reste un fichier dont l’extension est .jpg

Le type de fichier est retourné par le serveur via l’entête HTTP Content-Type (par exemple content-type: image/avif )

Note : il y a d’autres façons de diffuser des WebP sans réécriture, notamment avec la balise html <picture>

Ainsi une même url envoie un fichier différent selon la configuration de l’internaute.

Pour annoncer ce comportement correctement à tout ce beau monde (CDN / Proxy / Navigateur), l’extension WebP Converter for Media modifie 2 entêtes HTTP grâce à une règle dans un fichier .htaccess (situé dans wp-content/) :

  • vary: Accept : qui permet d’indiquer que le fichier en réponse peut varier en fonction de l’entête Accept transmise par le navigateur (et donc fonction du support de webP ou bien d’AVIF)
  • Cache-Control: Private : qui indique à un serveur de cache ou CDN de ne pas mettre en cache, mais indique qu’un navigateur (Chrome / Safari) peut stocker le fichier dans son cache.

Tout cela ne nous arrange pas, car nous aimerions pouvoir placer ces fichiers statiques dans le cache dans Varnish, mais nous voulons être sûrs de ne pas envoyer une version WebP à un navigateur qui ne le supporterait pas.

Par chance, la version de Varnish fournie par Infomaniak permet de délivrer un fichier différent selon l’entête Accept (ce qui, a priori, n’a pas toujours été le cas avec Varnish).

Après avoir bien vérifié que le Content-Type du fichier était différent selon le accept envoyé (voir commande en bas de page), alors, j’ai décidé de supprimer sur l’entête Cache-Control l’instruction « Private » si (et seulemement si) l’instruction « Public » était aussi présente (car les règles WP Rocket et extension WebP intervenaient les deux sur cet entête).

Cela rétablit ainsi la volonté de cache de WP Rocket, tout en fournissant un fichier différent pour respecter la volonté de l’extension WebP.

Attention : si vous souhaitez utiliser un CDN devant Varnish, typiquement Cloudfare en version gratuite, alors la ressource sera toujours la même car vary ne sera pas pris en charge (c’est possible en version pro via l’API, mais plus complexe à mettre en œuvre).

La première ressource délivrée sera celle qui sera délivrée les fois suivantes, ce qui pose des problèmes (par exemple, délivrer un fichier AVIF a un navigateur qui ne peut pas l’interpréter).

Certains CDN doivent respecter nativement cet entête, si vous en connaissez, laissez moi un commentaire.

3. Installer WP Rocket et le configurer pour Varnish

WP Rocket est une extension premium, qui me donne depuis plusieurs années, entière satisfaction et qui finalement ne coûte pas très cher pour le service apporté.

Si cela vous tente d’essayer, n’hésitez pas à passer par mon lien d’affiliation.

À propos de l’affiliation sur ce site : j’écris, avec plaisir des articles concernant WordPress et ne suis payé par personne pour le faire. Je ne propose via affiliation que des extensions et services que j’utilise au quotidien et que je peux recommander les yeux fermés. Ainsi, si vous achetez via mon lien, vous ne paierez pas plus cher et je toucherais peut-être quelques menus euros en remerciement du temps passé.

Pour WP Rocket, vous allez devoir activer l’add-on Varnish qui est inclus dans l’extension principale. Les concepteurs ont d’ailleurs écrit une documentation pour l’utilisation avec Varnish.

Activer l’addon Varnish dans WP Rocket

Le cas particulier des documents HTML

Dans cette configuration de base, les documents HTML (vos pages WordPress donc), ne seront pas mis en cache par Varnish (X-Cache Miss when WP Rocket is activated [EN]).

En effet, pour éviter des problèmes de raffraichissement de cache, WP Rocket a fait le choix assumé (et justifiable) de définir une date d’expiration à 0 sur les documents HTML. Or Varnish, dans ce cas précis, ne voudra pas mettre en cache la page.

La solution : installer une petite extension (helper) fournie par WP Rocket pour retirer cette règle spécifique aux HTML :

Attention : en retirant le expire sur les fichiers HTML. Le cache sera plus persistent sur le navigateur du client (et devra nécessiter un Actualisation forcée((Actualisation forcée : Généralement, on peut rafraîchir une page web en appuyant sur F5 ou sur Cmd + R sous Mac. L‘actualisation forcée va, elle, nécessiter l’appui d’une touche supplémentaire, la touche shift. Cela aura pour effet de rafraîchir en vidant le cache du navigateur. Il y a d’atres méthodes)) dans le navigateur pour avoir la dernière version).

Une meilleure alternative avec moins de persistance du cache

Sur le slack WordPress francophone, Nicolas Juen, directeur technique de Be API, m’a suggéré une alternative que je trouve très élégante. Au lieu de laisser WP Rocket générer des fichiers HTML pour qu’ils soient ensuite mis en cache par Varnish, il est possible de désactiver cette fonction dans WP Rocket. Ils ont développé ce mu-plugin Be API – WP Rocket handles Varnish à cette fin.

La partie qui nous intéresse est celle qui désactive la mise en cache des pages HTML (WP ROCKET : Disabling Page Caching [EN])

<?php
/**
 * Disable page caching in WP Rocket.
 *
 * @link http://docs.wp-rocket.me/article/61-disable-page-caching
 */
add_filter( 'do_rocket_generate_caching_files', '__return_false' );Langage du code : PHP (php)

La page sera ainsi bien mise en cache et délivrée par Varnish, mais le navigateur ne la stockera pas sur le poste local (il faut bien vérifier l’absence de l’entête expire dans ce cas).

Si vous n’avez pas WP Rocket

Vous pouvez tout à fait utiliser l’extension Proxy Cache Purge disponible sur le répertoire des extensions WordPress. Je l’ai testé dans cette configuration et elle fait également très bien le travail. Cette extension vous permet uniquement de déclencher une requête de purge à Varnish.

Pensez juste bien à regarder la date d’expiration des documents HTML si vous voulez qu’ils soient mis en cache par Varnish.

4. Installer le fichier de configuration Varnish sur Infomaniak

Cette partie est très simple et vous n’avez quasiment rien à faire si ce n’est de copier coller le contenu du fichier de configuration dans l’outil fourni de configuration par Infomaniak.

Mise en place du fichier de configuration .vcl sur Infomaniak

Pas d’inquiétude, si votre fichier de configuration .vcl est incorrect, le réglage ne se validera pas et vous recevrez une erreur par e-mail (mais la configuration précédente fonctionnelle restera en place).

5. Activer Varnish pour le site en question

Maintenant que le site est prêt, on peut rediriger le trafic vers Varnish.

Pour cela, chez Infomaniak, cela passe par la configuration de HAProxy. Chaque site de votre serveur est présenté dans la liste, et il vous suffit de choisir Varnish en lieu et place d’Apache.

6. Tester le bon fonctionnement du cache

Pour tester le bon fonctionnement de la mise en cache par Varnish, le plus simple est regarder les headers HTTP. Vous pouvez consulter ces infos par différents moyens, et même via des outils en ligne, mais mes préférés sont :

  • La ligne de commande :
    Par exemple avec la commande suivante curl -IL  'https://www.reuhno.fr/'
  • Votre navigateur web :
    grâce aux outils de développement (section « Réseau »). Si vous utilisez le navigateur Safari sous Mac, il faut au préalable activer les outils de développements.

Les valeurs à regarder

  • age : Est un compteur qui s’incrémente du nombre de secondes depuis la mise en cache de la resssource. Ainsi juste après un flush, on obtient 0
  • via : Indique que la ressource a été traitée via Varnish
  • x-cache : La valeur HIT indique que la page reçue provient bien du cache de Varnish. MISS indique au contraire que la page n’est pas mise en cache (il peut y avoir plusieurs raisons à cela(((Un MISS sera renvoyé à la première demande, lorsque le fichier n’est pas encore dans le cache et fera place à un HIT si la ressource est éligible. Si la ressource n’est pas éligible, alors elle renverra systématiquement un MISS.))).
  • x-cacheable : Donne plus de détails sur la mise en cache. C’est une valeur que nous définissons grâce à la logique dans notre fichier de config .vcl.
    Ce n’est pas un entête que l’on retrouve très souvent et je m’en sers à des fins de débogage.
  • x-varnish : Lorsque deux valeurs chiffrées et séparées par un espace sont affichées, alors la page issue du cache de Varnish. S’il n’y a qu’une valeur, alors la page est traitée par Varnish, mais n’est pas servie depuis le cache.

Vous pouvez également utiliser des services en ligne pour vérifier rapidement le statut de cache de la page https://isvarnishworking.co.uk/

7. Tester le bon fonctionnement de la purge

C’est souvent là le point sensible. Tout fonctionne correctement, et le site est rapide, mais que se passe-t-il si l’utilisateur met du contenu à jour ?

L’idée est toute bête, le serveur Varnish attends une commande de purge.

Concrétement, c’est un appel au serveur, mais au lieu d’utiliser la méthode GET (ou POST), la méthode attendue est PURGE.

Il y a d’autres méthodes de purge, notamment le BAN avec la prise en charge de REGEX((REGEX : Expressions régulières)), ce sont les méthodes employées par les extensions de WordPress, afin d’invalider le cache de plusieurs pages en une seule requête.

Je ne m’étendrai pas sur ce sujet, car nous partons du principe ici que les extensions de purge (WP Rocket ou Proxy Cache Purge) ont déjà implémenté ces méthodes correctement.

Purger en ligne de commande

En ligne de commande, et si vous avez bien autorisé votre IP à l’étape 2, il vous suffit de saisir la commande suivante dans votre terminal :

curl -X PURGE 'https://www.reuhno.fr/'Langage du code : Bash (bash)

En réponse du serveur, vous devriez obtenir un code 200 Purged

Si ce n’est pas le cas, revérifiez bien en premier lieu que votre IP est autorisée dans la configuration .vcl.

Automatiser la purge via WordPress

Avec WP Rocket, il vous suffit d’activer le module Varnish, puis de cliquer sur « Vider le cache », comme vous le feriez normalement sans Varnish.

Sans WP Rocket, la meilleure alternative reste d’installer l’extension Proxy Cache Purge qui se chargera d’envoyer la requête au moment.

Une fois la requête envoyée, je vous invite a tester à nouveau la présence des headers comme vu dans le point précédent.

Vérifications dans le navigateur

Une illustration vaut parfois mieux qu’un long discours. Voici comment je procède pour vérifier que la purge s’est bien déroulée.

Tester l’âge d’une page suite à une purge. La valeur age doit repasser à 0.

Le préchauffage du cache / warmup

Lorsque Varnish est purgé, il est donc vierge de toute ressource et est prêt à les stocker en mémoire.

Le premier client à demander la ressource n’aura donc pas la « chance » de bénéficier d’une version mise en cache.

Dans le cas de Varnish, cela se matérialisera par une réponse x-cache: MISS dans les headers HTTP.

Dès le deuxième passage, à condition que la ressource soit éligible au cache (selon les règles que nous avons définies), alors la réponse sera un x-cache: HIT

Pré-chargement de WP Rocket

WP Rocket a mis en place un mécanisme de préchargement. En effet, sous certaines conditions, un processus vient parcourir les pages HTML du site pour générer un fichier statique.

Ce fichier par la même occasion pourra être être mis en cache par Varnish.

Néanmoins, il ne me semble pas que WP Rocket fasse de préchargement sur les ressources statiques autres que HTML (ce qui en dehors du cadre de Varnish n’aurait pas de sens).

J’ai analysé les logs serveur et n’ai rien vu en ce sens. On voit donc que ce n’est pas suffisant pour assurer un vrai « warmup ».

Limites du préchauffage

Si vous avez tout bien suivi plus haut, lorsqu’une ressource est concernée par un entête vary plusieurs ressources peuvent être délivrées pour une même URL.

Si votre premier visiteur était (au hasard) un vieux navigateur web qui ne supporte que les images au format JPEG, alors la version stockée par Varnish sera le JPEG. Il faudra attendre un visiteur/crawler qui accepte le WebP pour que la version adéquate soit stockées elle aussi.

Il serait envisageable d’utiliser un crawler qui annonce différents en-têtes accept ou accept-encoding

Cela existe peut-être (et sûrement) déjà. Si je trouve des ressources à ce sujet, je n’hésiterai pas à actualiser cet article.

Points de Vigilance

  • Les IP autorisées à vider le cache doivent être bien renseignées (!)
    Dans le cas d’Infomaniak, j’ai mis du temps à comprendre que lors d’une demande de purge via WP Rocket (ou autre), l’IP qui fait la demande n’est pas une IP de type localhost, mais l’IP publique du serveur.

    Ainsi, j’ai rajouté celle-ci (IP v4 + IP v6) dans la configuration VCL.
  • Les headers HTTP renvoyés par votre applicatif doivent être bien paramétrés :
    • cache-control (private / public)
    • vary
    • expires

      Par chance WP Rocket fait cela plutôt bien. J’ai, par contre, dans ma configuration dû gérer une subtilité liée au plugin qui délivre les WebP et qui rajoutait un cache-control:private (et il a ses raisons de le faire).

  • Dans ce script .vcl, dans la subroutine vcl_deliver, des informations sur l’état de cache de la requête sont délivrés. Par exemple :
    • set resp.http.X-Cacheable = “YES”
    • set resp.http.X-Cacheable = “NO:UNCACHEABLE”;

      Ces infos sont très utiles le temps de la mise en place de Varnish, cela permet d’identifier rapidement si le cache a le bon comportement.

      Par contre, sur un site en production, il peut être préférable de cacher ces informations. C’est d’ailleurs ce que raconte l’auteur de ce billet de blog [EN] qui fait état de l’exploitation de cette information (X-Cache : HIT / MISS) par un attaquant afin de mener une attaque de type DDOS. En se basant sur le statut du cache, il est facile d’identifier les requêtes qui peuvent atteindre l’application et donc générer une charge système.

      Néanmoins, je me dois de vous rappeler que si vous êtes en train de lire cet article, il y a de fortes chances pour que vous n’ayez pas Varnish en place et que votre serveur soit, de fait, plus exposé à ce type d’attaque.

Commandes utiles

Inspecter les entêtes

Pour voir les entêtes, sans avoir à utiliser l’inspecteur réseau de votre navigateur, vous pouvez lancer la commande suivante dans votre terminal local :

curl -IL  https://www.reuhno.fr/Langage du code : Bash (bash)

Pour voir l’entête avec un header spécifique, par exemple avec le support du WebP :

curl -IL --header "Accept: image/webp" https://www.reuhno.fr/image-webp.jpgLangage du code : Bash (bash)

Voici pour info à quoi ressemble le header accept sur mon navigateur du moment :

accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8

Note : Si vous n’avez pas CURL sous Mac, vous pouvez l’installer grâce à HomeBrew (article à venir prochainement).
Pour Windows, vous pouvez installer la librairie CURL depuis le site officiel.

Statistiques de fonctionnement de Varnish

Vous pouvez vérifier les statistiques de mise en cache de Varnish avec la commande suivante :

varnishstat

Vous pouvez également voir le classement des entrées du journal de Varnish avec la commande suivante :

varnishtop

Autres ressources utile :

Schéma sur le flux de fonctionnement et les différentes subroutines : https://www.section.io/blog/varnish-cache-tutorial-vcl/ [EN]

Comment configurer le comportement du cache Varnish ?
https://blog.link-value.fr/configurer-varnish-df404f2de2de

Conclusion

Varnish, de par sa configuration, fait parfois un peu peur, mais le gain de performances pour délivrer les pages ne laisse pas de place au doute.

Je suis heureux d’avoir cette solution à ma disposition chez Infomaniak, sur un serveur clef en main, que je n’ai pas besoin d’administrer au quotidien.

Lors d’épisodes de fort trafic, le processeur du serveur est fortement épargné. En effet, délivrer des fichiers statiques n’est pas le point fort (Nginx est bien meilleur en cela);

Si vous avez aimé cet article et qu’il vous été utile, commentez, partagez, et n’hésitez pas à me payer un café.

Un grand merci à Pierre Lannoy, membre de communauté WordPress et concepteur des plugins extensions PerfOps One pour sa relecture attentive et ses remarques très pertinentes.

N’hésitez pas à passer par mes liens d’affiliation si vous souhaitez découvrir Infomaniak ou WP Rocket :

  1. Théoriquement, il est possible de rajouter ici une plage d’IP, par exemple celle du réseau Infomaniak avec un masque grâce à la notation CIDR (Par exemple : 195.15.0.0/24) []
Partagez cet article
Renaud
Renaud

Passionné d'internet depuis le plus jeune âge, je me suis lancé dans le développement web en 2008, et je me suis spécialisé dans WordPress depuis lors.

Je suis là pour vous aider dans votre stratégie en ligne, de la planification à la mise en œuvre et à l'optimisation technique et SEO. Travaillons ensemble pour faire de votre site web un succès !

Publications: 12

2 commentaires

  1. Le contenu de l’article est incroyable. Je suis exactement dans la meme situation que toi (infomaniak + woocommerce) et je n’ai jamais osé commencer a comprendre le fonctionnement de varnish. J’ai pu le faire grâce a toi et un tout grand merci pour ca. J’avais juste une erreur lors du flush sur wprocket au départ, mais c’etait du au fait que j’avais renseigné l’adresse IP du serveur cloud. Je n’avais pas remarqué que le site web avait une adresse IP fixe différente. Mais sinon ca roule. Si tu as trouvé une solution pour préchauffer le cache je suis preneur. Et si tu as une configuration opitmal de wprocket, je suis également preneur :-).

    Merci encore

    • Un grand merci pour ton commentaire.

      Hélas, je n’ai pas retravaillé sur cette config. Les sites qui sont paramétrés ainsi tournent très bien sans avoir rien touché. Et le préchauffage du cache absent, je m’en suis accommodé 😉

      Pour les config de wp-rocket, pas de recette toute crue car chaque site a ses particularités et sources de bugs différentes selon les configs. Donc c’est pas mal de tests et au coup par coup.

      A savoir tout de même que je délaisse un peu ce type de config pour les nouveau sites. Je passe en effet pas mal de sites sous Nginx avec l’aide de SpinUp Wp (sur des vps classiques).

      En tout cas je suis heureux que l’article ait pu servir a quelqu’un, j’avais pas mal bataillé a l’écrire !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *