Ce guide s’adresse aux responsables d’ingénierie qui font déjà tourner WordPress sur AWS (Amazon Web Services) et qui ont besoin qu’il absorbe plus de trafic sans flancher. Les signes avant-coureurs sont connus. Un serveur t3 unique, une machine virtuelle EC2 à performance variable qui ralentit une fois ses crédits de processeur épuisés, cale sous le trafic d’un lancement. Acheter un plus gros serveur ne fait que gagner du temps, et une seule mise à jour de plugin ratée peut déclencher une vague d’erreurs 502.
La solution consiste à rendre les serveurs WordPress sans état, c’est-à-dire qu’ils ne gardent aucune donnée à eux, puis à en faire tourner plusieurs derrière un répartiteur de charge. Les sessions et le cache passent dans ElastiCache Redis (le magasin en mémoire géré d’Amazon) et les téléversements dans S3 (le stockage de fichiers d’Amazon). La base de données passe dans RDS Multi-AZ, un MySQL géré avec une copie de secours dans une deuxième zone de disponibilité (un centre de données AWS distinct). CloudFront, le réseau de diffusion de contenu d’Amazon, sert ensuite la plupart des pages et des médias à partir du cache. Les sections ci-dessous suivent l’ordre que prennent la plupart des équipes. On passe d’un serveur unique à une base de données séparée, puis à un groupe Auto Scaling (ASG) derrière un Application Load Balancer (ALB), puis aux couches de cache et de stockage. À la fin, vous aurez une architecture de référence qui survit à la perte d’un serveur et qui grandit en ajoutant des serveurs plutôt qu’en achetant de plus gros.
Les mauvais points de départ
EC2 unique avec MariaDB colocalisée. C’est ce que livrent toutes les images Bitnami et Lightsail. Cela fonctionne pour un site de loisir. En production, cela casse au premier redémarrage de l’instance : caches froids, MySQL réclame mysql_upgrade, l’opcache se réchauffe à partir du disque pendant que le trafic s’accumule devant PHP-FPM. Aucune bascule possible. L’instantané horaire que vous payez est votre seul plan de reprise, et le restaurer perd toutes les commandes passées depuis 4 h.
Lift-and-shift depuis Dreamhost / WP Engine / cPanel mutualisé. Les équipes qui ont dépassé leur hébergement mutualisé clonent souvent l’arborescence sur une grosse EC2 et appellent cela une migration. Même anti-pattern : un répertoire /wp-content/uploads/ gras sur EBS qui dépasse 200 Go, des plugins qui écrivent des sessions dans /tmp, des tâches cron exécutées à l’intérieur du cycle de requête PHP. Vous héritez de tous les choix que l’hébergement mutualisé vous cachait, plus d’une facture bien plus salée.
Tenter de mettre à l’échelle un WordPress avec état. C’est la migration ratée classique. L’équipe d’ingénierie enveloppe l’EC2 existante dans un Launch Template, dirige l’ALB vers elle, configure l’ASG entre deux et huit instances. Elle voit ensuite les sessions s’évaporer pendant que les utilisateurs rebondissent entre nœuds, que les téléversements disparaissent dans le stockage éphémère, et que les mises à jour de plugins ne s’appliquent que sur un seul des quatre serveurs. L’autoscaling sans avoir préalablement rendu l’application sans état est du code infrastructure gaspillé.
Le correctif est structurel : épingler WordPress au rôle de gestionnaire de requêtes sans état et déplacer chaque morceau d’état — sessions, téléversements, cache, BD — vers un service infogéré qui vit en dehors du cycle de vie de l’instance.
Architecture de référence
Couche par couche
Bordure / DNS — Route 53, CloudFront, ACM
La zone DNS vit dans Route 53 avec les enregistrements A à l’apex et CNAME pour www aliasés vers la distribution CloudFront. Le certificat de CloudFront doit être émis dans us-east-1, peu importe où le reste de la pile vit — CloudFront ne consomme que des certificats ACM du point d’accès global. Émettez un second certificat ACM dans la région de base (par exemple ca-central-1) pour l’ALB.
CloudFront se place devant tout ce qui n’est pas un chemin d’administration. Comportements de cache :
/et chemins de contenu — cache par URL + langues acceptées, respect duCache-Controlde l’origine, TTL par défaut 1 h, min 0, max 1 jour./wp-admin/*,/wp-login.php,/wp-json/*— cache désactivé, tous les en-têtes transmis, tous les témoins transmis./wp-content/uploads/*— cache permanent, ignorer les chaînes de requête sauf une petite liste blanche utilisée pour le redimensionnement d’images.
Ajoutez AWS WAF à la distribution avec les ensembles de règles infogérés AWSManagedRulesCommonRuleSet, AWSManagedRulesKnownBadInputsRuleSet et AWSManagedRulesPHPRuleSet. Règles sur mesure : limiter le débit de POST /wp-login.php à 10 requêtes par IP toutes les 5 minutes, et POST /xmlrpc.php à zéro sauf besoin documenté.
Couche applicative — ALB + ASG (Launch Template), gabarit d’instance, stratégie d’AMI
L’ALB est exposé à internet dans des sous-réseaux publics, écoute sur 443 avec le certificat ACM régional, redirige HTTP vers HTTPS sur le port 80, et achemine vers un groupe cible de nœuds WordPress. Chemin de surveillance : /health.php (un fichier d’une ligne qui renvoie 200 uniquement lorsque PHP-FPM, Redis et la BD d’écriture répondent ; ne pas pointer la surveillance vers / car chaque vérification frapperait alors la BD et le CPU s’envolerait pendant un rafraîchissement de flotte). Délai de dérégistration : 30 secondes — assez long pour que les requêtes admin en cours se terminent, assez court pour que la réduction d’échelle ne bloque pas.
Le groupe Auto Scaling couvre les deux AZ, minimum 2, désiré 2, maximum 6 (dimensionné au plancher et au plafond de votre trafic). Type d’instance : commencez par t3.medium pour des nœuds sous 200 requêtes/s ; passez à m6i.large ou c6i.large lorsque PHP-FPM sature une seule instance. Évitez les types à crédits pour toute couche où l’épuisement des crédits se traduit par une latence visible — gardez t3 pour la mise à l’essai, pas la production.
La stratégie d’AMI est la décision qui sépare les déploiements rapides des lents :
- AMI préconstruite. Construisez l’AMI dans l’intégration continue avec Packer. Le cœur WordPress, les plugins, le thème, PHP, nginx et la base de code WordPress sont tous précuits. Cloud-init n’écrit que les valeurs d’environnement de
wp-config.phpdepuis SSM Parameter Store / Secrets Manager. Temps de démarrage : ~45 secondes du lancement de l’instance au statut « en service ». - Cloud-init depuis une AMI de base. Chaque instance récupère le code depuis S3 ou git au démarrage. Temps de démarrage : 3 à 6 minutes. Risque : dès que votre bucket S3 de déploiement est inaccessible, tout l’ASG ne parvient plus à monter en charge.
Utilisez des AMI préconstruites. La construction Packer s’exécute en intégration continue à chaque fusion sur main, étiquette l’AMI résultante avec le SHA git, et la version du Launch Template est incrémentée pour pointer vers la nouvelle AMI. Ensuite, un rafraîchissement progressif de l’ASG remplace la flotte.
Drainage gracieux à la terminaison : raccordez le crochet de cycle de vie de l’ASG à un document SSM qui envoie SIGQUIT à PHP-FPM, attend 25 secondes, puis poursuit la terminaison. Combiné au délai de dérégistration de 30 secondes de l’ALB, cela empêche les requêtes en cours d’être tuées en pleine réponse.
Couche cache — ElastiCache Redis
Un groupe de réplication ElastiCache Redis unique avec un primaire et au moins une réplique dans la seconde AZ, bascule Multi-AZ activée. Moteur 7.x. Chiffrement au repos et en transit activé. Le cluster Redis porte trois charges :
- Sessions.
wp-session-managerou tout drop-in qui écrit les sessions WordPress dans Redis plutôt qu’en lignes_options. Préfixe de clésess:, TTL d’une heure glissante. - Cache d’objets. Le drop-in
object-cache.phpstandard deWP RedisouObject Cache Pro. Préfixeobj:, pas de TTL global — évictionallkeys-lru. C’est le plus grand levier de performance pour un WordPress chargé en administration. - Cache de page complet. Optionnel mais à fort impact si votre trafic est majoritairement anonyme.
Batcacheou un plugin de cache page sur mesure qui stocke le HTML rendu dans Redis indexé par URL + état des témoins.
Épinglez un seul primaire Redis sauf si vous avez activement besoin du mode cluster pour des raisons de capacité (>50 Go d’ensemble de données ou >100 000 ops/s). Le mode cluster introduit des contraintes de hash-slot qui cassent tout plugin faisant KEYS sess:* ou des MULTI/EXEC à travers des clés non liées. Object Cache Pro gère le mode cluster correctement ; de nombreux plugins gratuits non.
Couche données — RDS Multi-AZ vs Aurora ; points d’accès lecteurs
Le choix sûr par défaut est RDS pour MySQL avec Multi-AZ activé, db.r6g.large au minimum pour la production. Sauvegardes automatiques activées avec rétention de 14 jours. Politique d’instantanés : quotidien plus à la demande avant chaque mise à jour du cœur WordPress. Performance Insights activé. Le standby Multi-AZ est un standby chaud complet — bascule en moins de 60 secondes, pas de changement de code applicatif requis, le point d’accès d’écriture résout simplement vers la nouvelle instance.
Aurora MySQL devient le meilleur choix dès que vous franchissez l’un des seuils suivants : plus de deux répliques de lecture nécessaires, IOPS d’écriture systématiquement au-dessus de ce qu’un volume gp3 soutient à votre taille provisionnée, répliques de lecture multi-régions requises. Le quatrième seuil est le besoin du flux instantané + clone pour les environnements de mise à l’essai. Les écrivains Aurora ne sont pas Multi-AZ au sens RDS — la couche de stockage est répliquée sur trois AZ mais l’instance d’écriture vit dans une AZ. La bascule remplace l’écrivain par une réplique, prenant 30 à 120 secondes.
WordPress peut utiliser un découpage écriture + lecture via HyperDB ou LudicrousDB, mais la plupart des piles de production écrivent et lisent simplement via le point d’accès d’écriture, en s’appuyant sur le cache d’objets pour absorber les lectures. N’introduisez un découpage en lecture que lorsque le CPU BD sur l’écrivain est la contrainte limitante et que vous avez prouvé qu’ajouter des nœuds de cache d’objets ne change pas l’aiguille. Voir l’article comparatif sur la couche de données lié au bas pour le cadre de décision entre RDS, Aurora et Aurora Serverless v2.
Stockage d’objets — S3 + WP Offload Media
Le bucket de téléversements vit dans la même région que l’ALB. Bloquez tout accès public, utilisez un contrôle d’accès à l’origine CloudFront pour permettre à CloudFront de lire le bucket, et servez les médias via la même distribution CloudFront sous /wp-content/uploads/*. Le plugin de choix est WP Offload Media Lite (gratuit, suffisant) ou son édition payante pour des fonctionnalités supplémentaires comme la livraison par défaut vers S3.
Deux pièges récurrents :
- ACL sur les anciens buckets. Les nouveaux buckets S3 ont les ACL désactivées par défaut (« Propriété d’objet : Propriétaire du bucket imposé »). Les buckets plus anciens acceptent les écritures ACL ; si le plugin tente de positionner
public-readsur chaque téléversement alors que les ACL sont désactivées, le téléversement échoue silencieusement à être servi. Correctif : configurer le plugin en mode « sans ACL » et servir via CloudFront avec OAC. - CORS pour les prévisualisations dans l’éditeur. Les éditeurs qui téléversent via la bibliothèque média voient des vignettes brisées lorsque la distribution CloudFront ne transmet pas l’en-tête
Origin. Soit le transmettre explicitement, soit configurer le CORS du bucket pour autoriser lesGETdepuis l’origine d’édition et configurer CloudFront pour honorer la réponse CORS.
Recherche — OpenSearch seulement si nécessaire
La recherche WordPress est LIKE %query% contre la table des articles. Elle s’effondre passé environ 50 000 articles. Si la recherche est critique pour l’expérience utilisateur (publication, presse, commerce électronique), ajoutez OpenSearch via ElasticPress et un domaine OpenSearch dans le même VPC. Pour le reste, sautez OpenSearch — le coût et la surface opérationnelle n’en valent pas la peine. Le cache d’objets absorbera la charge de recherche jusqu’à ce que le corpus soit véritablement volumineux.
Rendre WordPress sans état (la partie que la plupart des guides omettent)
C’est ici que la plupart des migrations échouent. L’architecture ci-dessus ne fonctionne que si WordPress lui-même cesse d’écrire de l’état sur son propre système de fichiers. La liste de vérification :
wp-config.phplit depuis l’environnement. Aucun identifiant BD codé en dur, aucun mot de passe Redis codé en dur, aucune clé codée en dur.getenv('DB_PASSWORD')pour chaque secret. Le user-data du Launch Template écrit ces valeurs d’environnement depuis SSM Parameter Store (ou Secrets Manager pour les identifiants à rotation). La même règle s’applique àWP_CACHE_KEY_SALT,WP_REDIS_PASSWORDet à la familleAUTH_KEY.- Sessions dans Redis, pas sur le système de fichiers. Déposez un plugin de sessions vers Redis dans
mu-plugins/afin qu’il se charge avant qu’un autre plugin ne puisse écrire dans/tmp. - Téléversements dans S3. WP Offload Media configuré en « copier vers S3 + retirer du local » pour que le volume EBS ne porte jamais les téléversements de production. Les téléversements préexistants doivent être migrés via les outils du plugin avant de commencer à monter l’ASG en charge.
- Cron externe. Ajoutez
define('DISABLE_WP_CRON', true);àwp-config.php. Mettez en place une règle EventBridge toutes les 5 minutes qui POST vershttps://example.org/wp-cron.php?doing_wp_cron=1via l’ALB. Sans cela, chaque requête visiteur a une chance de déclencher le cron, qui s’exécute alors sur quelque nœud que la requête a atteint — non déterministe et pics de ressources. - Journaux vers CloudWatch. L’agent CloudWatch expédie les journaux d’accès/d’erreur nginx et les journaux PHP-FPM vers un groupe de journaux. Configurez un filtre de métrique pour les HTTP 5xx et une alarme CloudWatch sur le taux de 5xx.
- Code des plugins et thèmes précuit dans l’AMI, pas installé à l’exécution. Aucune mise à jour de plugin depuis
/wp-admin/. Le flux de mise à jour est : le développeur met à jourcomposer.jsonou le manifeste de déploiement, l’intégration continue construit une nouvelle AMI, l’intégration continue incrémente le Launch Template, un rafraîchissement d’ASG déploie le changement. Mises à jour automatiques du cœur WordPress : désactivez avecdefine('WP_AUTO_UPDATE_CORE', false);etadd_filter('automatic_updater_disabled', '__return_true');.
Si l’un de ces points est omis, l’architecture fuit de l’état et les symptômes apparaissent en production. On voit alors des utilisateurs déconnectés lors des événements de mise à l’échelle, la moitié de la flotte a le nouveau plugin et l’autre non, les résultats de recherche varient selon le nœud, ou les téléversements depuis l’éditeur ne sont jamais visibles pour les visiteurs.
Modèle de déploiement
ASG bleu/vert avec surveillance et rafraîchissement progressif :
- L’intégration continue construit une AMI Packer étiquetée avec le SHA git.
- L’intégration continue crée une nouvelle version de Launch Template pointant vers la nouvelle AMI.
- L’intégration continue met à jour l’ASG pour utiliser la nouvelle version du Launch Template.
- L’intégration continue déclenche
start-instance-refreshavec--preferences MinHealthyPercentage=90,InstanceWarmup=120. - L’ASG termine une instance à la fois, l’ALB la draine, une nouvelle instance se lance depuis la nouvelle AMI, s’enregistre dans le groupe cible, devient saine, puis l’instance suivante est remplacée.
Ce qui va dans user-data vs dans l’AMI : seuls les éléments qui doivent varier par instance vont dans user-data (métadonnées d’instance, nom d’hôte, préfixe de paramètre SSM propre à l’environnement). Tout ce qui ne varie pas — code WP, plugins, configuration nginx, configuration de pool PHP-FPM — est dans l’AMI. User-data devrait tenir dans un écran de bash ; s’il dépasse 50 lignes, vous avez laissé fuir du travail d’AMI dans user-data et vos temps de démarrage s’en ressentiront.
Surveillance
La base d’alarmes CloudWatch qui attrape chaque défaillance courante :
| Alarme | Seuil | Notifier |
|---|---|---|
| Taux de 5xx ALB | >1 % sur 5 minutes | Sev-2 |
| p95 latence groupe cible ALB | >2 s sur 5 minutes | Sev-3 |
| Nombre d’hôtes non sains ALB | >=1 pendant 2 minutes | Sev-3 |
| CPU écrivain RDS / Aurora | >80 % pendant 10 minutes | Sev-3 |
| Délai de réplication RDS / Aurora | >30 s pendant 5 minutes | Sev-2 |
| Évictions ElastiCache | >0 soutenu pendant 10 minutes | Sev-4 |
| CPU ElastiCache | >80 % pendant 10 minutes | Sev-3 |
| Taille minimale ASG enfreinte | désiré < min pendant 5 minutes | Sev-2 |
| Taux de 5xx CloudFront | >0,5 % sur 5 minutes | Sev-2 |
L’alarme d’évictions est la plus utile : elle vous dit que le cache d’objets est trop petit pour l’ensemble de travail, ce qui est l’indicateur avancé que les opérations administratives chargées sont sur le point de frapper la BD au lieu de Redis.
Forme du coût
Les Reserved Instances ou les Savings Plans couvrent le plancher de l’ASG en régime établi — typiquement le plancher de deux instances qui ne descend jamais. Les instances marginales au-dessus du plancher restent à la demande. Aurora applique une tarification par IOPS qui croise le coût RDS gp3 quelque part autour de la troisième réplique de lecture ou lorsque les IOPS d’écriture soutenues dépassent ce qu’un seul volume gp3 RDS soutient à la même taille provisionnée. En dessous de ce point de croisement, RDS est moins cher ; au-dessus, Aurora est moins cher car vous cessez de surpayer pour des IOPS provisionnées que vous n’utilisez pas.
Le coût S3 + CloudFront pour les médias est presque toujours moins cher que les médias adossés à EBS sur les instances applicatives, parce qu’EBS s’échelonne par taille de volume tandis que S3 s’échelonne par octets stockés. La sortie CloudFront est la ligne le plus souvent oubliée — activez les journaux standard CloudFront et passez en revue mensuellement les 10 premiers chemins par octets sortants.
L’approche Stack Harbor consiste à cadrer selon les facteurs — volume de requêtes, ratio admin/anonyme, taille de la bibliothèque média, ensemble de plugins — et à proposer un forfait mensuel fixe après une revue de découverte. Publier un tarif par instance sur une pile WordPress de production serait trompeur car trois des facteurs de coût sont décidés par votre mélange de trafic, pas par le prix de liste AWS.
Pièges
- Sessions persistantes pour
wp-admin. L’administration WordPress fait un usage intensif des nonces liés à la session. Si votre stockage de session n’est pas adossé à Redis, vous devez activer les sessions persistantes ALB sur le chemin admin. Meilleur correctif : déplacez les sessions dans Redis et désactivez la persistance pour que n’importe quel nœud puisse servir n’importe quelle requête admin. .htaccesssur ALB..htaccessApache n’existe pas sur nginx. Traduisez chaque règle de réécriture et chaque règle d’accès dans la configuration du site nginx, et précuisez dans l’AMI. Le piège le plus courant est la règle de réécriture des permaliens WordPress — nginx a besoin detry_files $uri $uri/ /index.php?$args;dans le bon bloc location.- Téléversements de médias vers EBS éphémère. Même avec WP Offload Media, des plugins qui contournent le gestionnaire de téléversements (par exemple certains générateurs de PDF, certains outils d’import) écrivent directement dans
/wp-content/uploads/sur l’instance locale. Lorsque l’ASG remplace cette instance, le fichier est perdu. À résoudre en montant/wp-content/uploads/comme partage EFS si vous ne pouvez pas garantir que chaque écrivain passe par le plugin d’offload, ou en auditant les plugins pour confirmer qu’ils utilisent touswp_upload_dir()correctement. - Plugins qui écrivent dans
/wp-content/. Certains plugins de cache, de journalisation et de sécurité écrivent en dehors de/wp-content/uploads/— par exemple vers/wp-content/cache/,/wp-content/wflogs/ou/wp-content/upgrade/. Soit rendez ces chemins inscriptibles sur l’AMI et acceptez que les données soient par instance, soit configurez le plugin pour utiliser Redis ou S3 à la place. Le pare-feu WAF de Wordfence est le coupable le plus courant — passez-le en mode « extended protection » qui n’écrit pas sur le système de fichiers local. - Épuisement du pool de connexions. WordPress maintient une connexion BD par travailleur PHP-FPM par nœud. Avec quatre nœuds à 20 travailleurs chacun, vous pouvez atteindre 80 connexions ; RDS
db.r6g.largeplafonne par défaut à 1000, vous avez donc de la marge — mais si vous augmentez la taille du pool PHP-FPM pendant un pic, vous pouvez épuiser les connexions. RDS Proxy devant l’écrivain mutualise les connexions et absorbe les pics ; introduisez-le avant d’en avoir besoin.
Quand ce n’est pas la bonne architecture
Si votre équipe compte deux ingénieurs, que votre plancher de trafic est de 50 requêtes/s et que votre cadence de livraison est « toutes les six semaines », l’architecture ci-dessus est exagérée. Un seul hôte de type cPanel bien réglé avec un CDN devant vous servira mieux — voir gestion cPanel/WHM chez Stack Harbor pour ce parcours. Si votre application est principalement pilotée par les éditeurs et que le trafic est en rafale plutôt que soutenu, une plateforme WordPress infogérée avec les mêmes garanties d’absence d’état, mais opérée par une autre équipe SRE, ira plus vite — c’est l’offre hébergement WordPress infogéré.
L’architecture de cet article est la bonne quand vous avez besoin de mise à l’échelle horizontale sur les primitives AWS de base, que vous voulez garder le root dans votre propre compte AWS et que votre cadence de livraison justifie l’investissement en intégration continue, AMI et code infrastructure. Stack Harbor opère ce modèle sous gestion AWS — les ingénieurs principaux possèdent la conception et les escalades Sev-1, l’exploitation quotidienne s’opère contre un SLA, et la tarification est cadrée selon les facteurs et devisée après une revue de découverte plutôt que publiée par instance.
Voir aussi
- WordPress sur AWS : RDS MySQL vs Aurora vs Aurora Serverless — quelle couche de données convient
- Stockage de sessions Redis sur ElastiCache — le modèle qui survit à la bascule
- Sessions persistantes ALB : quand en avez-vous réellement besoin
- Déploiements bleu/vert sur un groupe Auto Scaling EC2
- WordPress headless avec un frontal Next.js
- Service Cloud PaaS — alternative infogérée quand opérer la pile en code infrastructure n’est pas le bon compromis
- Managed Operations — opérer la pile ci-dessus sous SLA Stack Harbor