Le site en question est un média d’actualité régionale qui publie une trentaine d’articles par jour et reçoit des pics de trafic brutaux lors d’événements locaux relayés par une application mobile avec notifications push. Avant notre intervention, chaque pic entraînait des ralentissements visibles, parfois des erreurs 502 côté serveur. L’objectif : tenir 2 millions de pages vues par jour sans changer de gamme d’hébergement, uniquement en revoyant l’architecture de cache.
Ce retour d’expérience ne couvre pas le réglage fin de PHP-FPM, qui a fait l’objet d’un travail séparé, mais se concentre sur les trois couches de cache mises en place et sur la façon dont elles s’articulent, en particulier au moment le plus critique : l’invalidation après publication.
Couche 1 : le cache de page en front
La première couche, un cache de page FastCGI côté Nginx, sert la quasi-totalité des visiteurs anonymes sans jamais toucher à PHP. La durée de vie du cache a été fixée à 300 secondes pour les pages d’article et à 60 secondes pour la page d’accueil, plus sensible à la fraîcheur du contenu. Ce choix résulte d’un compromis assumé : sur un média d’actualité, une fraîcheur parfaite en temps réel n’est pas indispensable, mais une page d’accueil vieille de dix minutes lors d’un événement local en direct l’est encore moins.
Couche 2 : le cache d’objets pour tout ce qui reste dynamique

Derrière le cache de page se trouve un cache d’objets Redis qui absorbe tout ce qui ne peut pas être mis en cache au niveau HTML complet : le compteur de commentaires en temps réel, le bloc « articles les plus lus » recalculé toutes les cinq minutes par une tâche cron plutôt qu’à chaque affichage, et les widgets de navigation communs à tout le site, mutualisés en un seul objet de cache plutôt que reconstruits par page.
Un point qui a fait gagner un temps précieux : le bloc « articles les plus lus », auparavant calculé par une requête SQL coûteuse à chaque affichage de page, est désormais recalculé une fois toutes les cinq minutes par une tâche planifiée via wp_schedule_event(), et le résultat stocké en cache d’objets avec une durée de vie légèrement supérieure à l’intervalle de recalcul, en filet de sécurité si la tâche cron est en retard.
Couche 3 : le CDN pour les images et les pics géographiques
Toutes les images passent par un CDN qui gère aussi les pics ponctuels côté HTML en cas de saturation du cache Nginx, via un cache de secours de courte durée (30 secondes) activé uniquement au-delà d’un certain seuil de requêtes par seconde. Ce mécanisme a évité le pire lors d’un pic mesuré à plus de 3 000 requêtes par seconde en trente secondes, déclenché par une notification push envoyée à 400 000 utilisateurs de l’application mobile.
Le trafic le plus dangereux n’est presque jamais celui de Google ou des réseaux sociaux, qui arrive de façon progressive. C’est celui d’une notification push envoyée à toute la base d’abonnés en un seul instant.
L’invalidation ciblée, le vrai sujet difficile
La question la plus délicate n’était pas de mettre en cache, mais de savoir quoi invalider et quand. Purger l’intégralité du cache à chaque publication d’article aurait provoqué, sur un site à ce volume, une vague de requêtes non mises en cache à chaque publication, avec le risque de recréer artificiellement les pics qu’on cherchait à éviter.
La solution retenue : un hook sur save_post et transition_post_status qui purge uniquement l’URL de l’article concerné, la page d’accueil et la ou les pages de catégorie associées, via l’API de purge ciblée du module de cache Nginx. Les autres pages du site, non concernées par la publication, gardent leur cache intact.
add_action( 'save_post', function( $post_id ) {
$urls = array(
get_permalink( $post_id ),
home_url( '/' ),
);
foreach ( get_the_category( $post_id ) as $cat ) {
$urls[] = get_category_link( $cat->term_id );
}
foreach ( $urls as $url ) {
wpm_purge_cache_url( $url );
}
} );
Résultat sur trois mois
Sur la période de suivi, le site a tenu ses 2 millions de pages vues quotidiennes, avec deux pics dépassant les 4 000 requêtes par seconde lors d’événements locaux majeurs, sans erreur 502 ni ralentissement perceptible côté visiteur, et sans changement de la configuration serveur d’origine.
En résumé
Trois couches de cache complémentaires, chacune avec sa propre durée de vie et sa propre logique d’invalidation, valent mieux qu’une seule couche agressive et globale. Le vrai travail d’architecture ne se situe pas dans le choix des outils, largement standard, mais dans la finesse de l’invalidation : purger exactement ce qui doit l’être, ni plus ni moins, au moment précis où le contenu change.