Vous cliquez sur « Mettre à jour le plugin ». Le spinner tourne. Puis WordPress plante avec : « Fatal error: Maximum execution time of 30 seconds exceeded ». Ou la page devient blanche et vous vous retrouvez à vous regarder dans l’écran comme si on vous devait de l’argent.
Cette erreur n’est pas WordPress qui fait sa diva. C’est votre serveur qui applique un arrêt forcé : une requête PHP a tourné plus longtemps que permis. L’astuce consiste à corriger cela sans faire « augmentez simplement à 300 secondes » et considérer l’affaire réglée — car de longs délais d’attente transforment de petits problèmes en incidents de production.
Ce que l’erreur signifie réellement (et ce qu’elle ne signifie pas)
Dans la plupart des stacks WordPress, « Max execution time exceeded » est lancée par PHP lorsqu’une requête tourne plus longtemps que la valeur de max_execution_time. C’est une limite PHP (par requête) conçue pour éviter qu’un code indéfini n’accapare le CPU indéfiniment.
Mais dans des systèmes réels, la limite de PHP n’est rarement le seul timeout en jeu. Vous avez généralement une pile de timers :
- Timeout du navigateur / client (l’utilisateur abandonne, ou le reverse proxy ferme la connexion).
- Timeouts du reverse proxy (Nginx, CDN, load balancer).
- Timeouts du serveur web (Apache, Nginx fastcgi).
- Timeouts PHP-FPM (
request_terminate_timeoutou réglages de pool). - Limite du script PHP (
max_execution_time). - Timeouts de la base de données (MySQL wait timeout, lock wait timeout).
- Appels externes (passerelle de paiement, SMTP, serveurs de licences).
Donc quand vous voyez l’erreur, n’assumez pas « augmentez max_execution_time et c’est fini ». Parfois PHP n’est que le messager. Le vrai coupable est une requête base de données lente, des tables verrouillées, un CPU surchargé, un I/O disque saturé, ou un plugin qui fait n’importe quoi comme redimensionner 200 images dans une seule requête web.
Prise de position : traitez les erreurs de temps d’exécution comme des alarmes incendie. Vous pouvez enlever les piles, oui. Mais peut-être vaut-il mieux vérifier pourquoi la cuisine brûle.
Une citation à garder à portée
« L’espoir n’est pas une stratégie. » — Gene Kranz
Oui, elle est populaire chez les équipes ops. Elle est aussi exacte. Deviner des timeouts, c’est de l’espoir. Mesurer, c’est une stratégie.
Pourquoi ça arrive : les vrais goulets d’étranglement
1) Famine CPU : PHP attend son tour
PHP n’est pas intrinsèquement lent, mais il est sensible à la contention. Si votre serveur a trop de workers PHP-FPM, chacun reçoit un tout petit peu de CPU. Une tâche qui « prend normalement 2 secondes » peut alors prendre 45. Boum : timeout.
Cela arrive fréquemment sur l’hébergement mutualisé, les VPS sous-dimensionnés, et les configurations « on a réduit la taille de l’instance pour économiser ».
2) I/O disque : le tueur discret
WordPress est bavard : lecture des fichiers PHP, chargement des plugins, écriture des sessions, mise à jour des options, génération de miniatures. Si le stockage est lent (volumes réseau, disques surutilisés, RAID dégradé, crédits de burst épuisés), PHP peut passer la majeure partie de son « temps d’exécution » bloqué sur de l’I/O.
3) Goulets base de données : requêtes lentes, verrous et réalité des migrations
Quand WordPress bloque, il attend souvent MySQL/MariaDB. Causes classiques :
- Indexes manquants introduits par des tables de plugins.
- Un
wp_optionsénorme avec des lignes autoload gonflées. - Contention de verrous lors d’importations, mises à jour en lot, ou tâches cron défaillantes.
- Un ALTER TABLE long en heures de production. Audacieux.
4) Appels externes : votre site n’est aussi rapide que cette API tierce
Passerelles de paiement, calculateurs de livraison, vérifications de licence, services d’optimisation d’images, fournisseurs d’e-mails — si un plugin effectue des appels HTTP synchrones pendant le chargement de la page ou les actions admin, il hérite de toute la fragilité d’Internet.
5) WP-Cron : « pas vraiment cron » signifie « parfois accumulation »
WP-Cron s’exécute sur les chargements de page à moins que vous ne le désactiviez et n’utilisiez un vrai cron. Si le trafic est faible, les tâches s’exécutent en retard puis s’accumulent. Si le trafic est élevé, les tâches peuvent s’exécuter trop souvent, se chevaucher et se disputer les ressources. Dans les deux cas, les tâche cron longues exécutées via des requêtes HTTP sont des candidates idéales aux timeouts.
6) Pression mémoire masquée en timeouts
Parfois la requête est lente parce qu’elle thrash la mémoire : allocations répétées, swap, GC, ou échecs répétés d’allocation pour le traitement d’images. Cela peut néanmoins remonter sous la forme d’un timeout d’exécution même si le problème sous-jacent est la mémoire.
Petite blague #1 : Les timeouts sont comme des échéances — tout le monde les ignore jusqu’à ce que le boss les applique.
Playbook de diagnostic rapide (vérifiez d’abord/puis/ensuite)
Si vous voulez trouver le goulet rapidement, faites-le dans cet ordre. Cela minimise les suppositions et maximise la « preuve ».
Premier : identifiez quel timeout a déclenché (PHP vs PHP-FPM vs serveur web)
- Si vous voyez « Fatal error: Maximum execution time of X seconds exceeded » dans les logs ou la sortie, le
max_execution_timede PHP a probablement déclenché. - Si vous voyez 502/504 au proxy, suspectez les timeouts de Nginx/Apache/proxy ou la terminaison PHP-FPM.
- Si vous voyez des lignes de log PHP-FPM comme « request terminated », suspectez
request_terminate_timeoutou une mise à mort au niveau du pool.
Second : décidez si c’est systémique (ressource) ou spécifique (un endpoint/tâche)
- Systémique : CPU saturé, load average élevé, latence disque élevée, beaucoup de requêtes lentes.
- Spécifique : toujours lors d’une mise à jour de plugin, d’une importation, d’une page d’administration particulière ou d’un checkout.
Troisième : prouvez l’attente (CPU vs disque vs BD vs réseau)
- CPU : file d’attente d’exécution élevée, nombreux workers PHP, long temps mur avec peu de temps CPU réel.
- Disque : iowait élevé,
awaitlent dansiostat. - BD : requêtes lentes, attentes de verrous, threads bloqués en
Sending dataouWaiting for table metadata lock. - Réseau/externe : requêtes bloquées dans des appels HTTP ; les logs du plugin montrent des timeouts distants.
Quatrième : choisissez le plus petit correctif sûr
La plupart du temps, le plus petit correctif sûr n’est pas « mettez tout à 600 secondes ». C’est l’un des suivants :
- Déplacer le travail lourd hors de la requête web (WP-CLI, jobs en arrière-plan).
- Corriger la requête lente / ajouter un index / réduire le bloat autoload.
- Dimensionner correctement les workers PHP-FPM selon le CPU et la mémoire dont vous disposez réellement.
- Corriger l’I/O disque ou migrer vers un stockage meilleur.
- Ajouter des timeouts et des retries raisonnables pour les appels externes, avec des solutions de repli.
Tâches pratiques : commandes, sorties et décisions (12+)
Ce sont les tâches « faites-le maintenant, apprenez quelque chose ». Chacune inclut une commande, une sortie d’exemple, ce que cela signifie et la décision à prendre. Suppose Linux avec systemd, Nginx ou Apache, et PHP-FPM. Ajustez les chemins selon votre distribution.
Task 1: Confirm PHP max_execution_time (CLI baseline)
cr0x@server:~$ php -i | grep -E '^max_execution_time'
max_execution_time => 30 => 30
Ce que cela signifie : PHP CLI est réglé à 30 secondes. Le SAPI web peut différer, mais cela vous donne une base.
Décision : Si le CLI affiche une valeur sensée (30–60) mais que les requêtes web ont des timeouts différents, concentrez-vous sur les configs PHP-FPM et serveur web plutôt que de deviner le php.ini global.
Task 2: Confirm PHP-FPM pool effective settings
cr0x@server:~$ php-fpm8.2 -tt 2>/dev/null | head -n 20
[27-Dec-2025 10:11:02] NOTICE: configuration file /etc/php/8.2/fpm/php-fpm.conf test is successful
[27-Dec-2025 10:11:02] NOTICE: fpm is running, pid 1162
[27-Dec-2025 10:11:02] NOTICE: ready to handle connections
Ce que cela signifie : La config est parseable. Pas une preuve des valeurs à l’exécution, mais cela évite les surprises du type « vous avez édité le mauvais fichier ».
Décision : Si le test de config échoue, arrêtez et corrigez la syntaxe. Les timeouts causés par un « service qui n’a pas rechargé » sont embarrassants et courants.
Task 3: Check Nginx FastCGI timeouts
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -E 'fastcgi_read_timeout|fastcgi_send_timeout|proxy_read_timeout' | head
fastcgi_read_timeout 60s;
fastcgi_send_timeout 60s;
Ce que cela signifie : Nginx attendra 60 secondes la réponse de PHP-FPM. Si PHP peut tourner 120 secondes mais que Nginx attend 60, vous verrez des 504 avant que PHP n’atteigne son propre timeout.
Décision : Alignez les timeouts. Le timeout du proxy/edge doit généralement être légèrement supérieur à celui de PHP pour les requêtes orientées utilisateur, sauf si vous voulez volontairement caper le temps de réponse.
Task 4: Check Apache timeout (if using prefork/event with proxy_fcgi)
cr0x@server:~$ apachectl -S 2>/dev/null | head -n 5
VirtualHost configuration:
*:80 is a NameVirtualHost
default server example.com (/etc/apache2/sites-enabled/000-default.conf:1)
cr0x@server:~$ sudo apachectl -t -D DUMP_RUN_CFG 2>/dev/null | grep -E '^Timeout'
Timeout: 60
Ce que cela signifie : Apache attend 60 secondes. Encore une fois : si PHP pense pouvoir tourner 120 secondes, le client ne le verra peut-être jamais.
Décision : Soit baissez PHP pour correspondre et forcez le traitement en arrière-plan, soit augmentez le timeout web uniquement pour des endpoints spécifiques (importations) et gardez le reste strict.
Task 5: Find the exact error in PHP-FPM and PHP logs
cr0x@server:~$ sudo grep -R "Maximum execution time" -n /var/log/php* /var/log/php8.2-fpm.log 2>/dev/null | tail -n 5
/var/log/php8.2-fpm.log:21944:PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/wp-includes/http.php on line 320
Ce que cela signifie : La trace pointe vers les requêtes HTTP de WordPress. C’est généralement un plugin/thème effectuant un appel externe, ou le core WordPress qui se parle à lui-même.
Décision : L’étape suivante est d’identifier quel plugin a déclenché l’appel (activez brièvement le logging WP debug, ou reproduisez avec le plugin désactivé).
Task 6: Observe live resource pressure (CPU, memory, iowait)
cr0x@server:~$ top -b -n 1 | head -n 12
top - 10:13:44 up 12 days, 2:17, 1 user, load average: 7.12, 6.95, 6.20
Tasks: 245 total, 2 running, 243 sleeping, 0 stopped, 0 zombie
%Cpu(s): 22.1 us, 3.2 sy, 0.0 ni, 63.4 id, 11.0 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 7842.1 total, 412.6 free, 6120.3 used, 1309.2 buff/cache
MiB Swap: 2048.0 total, 1880.0 free, 168.0 used. 1200.0 avail Mem
Ce que cela signifie : Un wa à 11% suggère une attente d’I/O disque. Le load average est élevé par rapport aux coeurs CPU (interprétez en conséquence), ce qui signifie que des files se forment.
Décision : Ne augmentez pas les timeouts tout de suite. Examinez la latence du stockage et la concurrence PHP-FPM (trop de workers peut amplifier la contention I/O).
Task 7: Measure disk latency with iostat
cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 12/27/2025 _x86_64_ (4 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.52 0.00 3.01 12.44 0.00 66.03
Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
nvme0n1 82.00 140.00 3200.0 8200.0 78.00 2.10 14.80 0.55 12.30
Ce que cela signifie : Un await autour de ~15ms n’est pas catastrophique, mais pour un WordPress chargé cela peut s’accumuler. Si vous voyez 50–200ms, c’est une alarme rouge.
Décision : Si le await est régulièrement élevé, corrigez le stockage (migrer vers un volume plus rapide, arrêter les voisins bruyants, investiguer un RAID/disk dégradé) avant de toucher aux timeouts PHP.
Task 8: Check PHP-FPM pool saturation
cr0x@server:~$ sudo ss -s
Total: 1123 (kernel 0)
TCP: 317 (estab 54, closed 233, orphaned 0, timewait 233)
Transport Total IP IPv6
RAW 0 0 0
UDP 6 4 2
TCP 84 47 37
INET 90 51 39
FRAG 0 0 0
cr0x@server:~$ sudo ps -o pid,pcpu,pmem,etime,cmd -C php-fpm8.2 --sort=-pcpu | head
PID %CPU %MEM ELAPSED CMD
1422 18.2 2.7 01:03 php-fpm: pool www
1418 15.9 2.6 01:01 php-fpm: pool www
1399 12.4 2.5 00:59 php-fpm: pool www
Ce que cela signifie : Plusieurs workers consomment du CPU depuis plus d’une minute, ce qui suggère des requêtes lentes ou des deadlocks. S’ils ont tous des temps écoulés similaires, vous avez peut-être une foule.
Décision : Vérifiez la page d’état PHP-FPM (si activée) ou les logs pour « server reached pm.max_children ». Si saturé, réduisez la concurrence ou ajoutez CPU/mémoire ; ne haussez pas simplement les timeouts.
Task 9: Inspect MySQL for slow queries and lock waits
cr0x@server:~$ mysql -e "SHOW PROCESSLIST\G" | head -n 30
*************************** 1. row ***************************
Id: 27
User: wpuser
Host: localhost
db: wordpress
Command: Query
Time: 41
State: Sending data
Info: SELECT option_name, option_value FROM wp_options WHERE autoload='yes'
*************************** 2. row ***************************
Id: 29
User: wpuser
Host: localhost
db: wordpress
Command: Query
Time: 39
State: Waiting for table metadata lock
Info: ALTER TABLE wp_posts ADD COLUMN foo int(11)
Ce que cela signifie : Une requête est lente (wp_options autoload). Une autre session est bloquée sur un verrou de métadonnées dû à une modification de schéma.
Décision : N’effectuez pas de changements de schéma en période de trafic actif. Auditez aussi le bloat wp_options autoload — c’est un classique d’auto-sabotage WordPress.
Task 10: Quantify autoloaded options size
cr0x@server:~$ mysql -N -e "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
12.47
Ce que cela signifie : 12.47MB d’options autoloadées est important. WordPress les charge à chaque requête. Félicitations : vous avez construit une petite base de données de configuration à l’intérieur de votre base de données de configuration.
Décision : Identifiez les plus gros responsables et mettez autoload sur ‘no’ lorsque c’est sûr, ou corrigez le plugin. Si vous dépassez ~1–2MB, vous payez généralement une taxe sur chaque vue de page.
Task 11: Find the largest autoload offenders
cr0x@server:~$ mysql -e "SELECT option_name, ROUND(LENGTH(option_value)/1024,1) AS kb FROM wp_options WHERE autoload='yes' ORDER BY LENGTH(option_value) DESC LIMIT 10;"
+------------------------------+-------+
| option_name | kb |
+------------------------------+-------+
| some_plugin_cache_blob | 2048.3|
| another_plugin_settings | 512.8 |
| rewrite_rules | 256.4 |
| widget_custom_html | 188.1 |
+------------------------------+-------+
Ce que cela signifie : Un plugin stocke un blob de cache en autoload (mauvais), et d’autres options sont volumineuses.
Décision : Pour les blobs de cache : migrez vers un cache d’objets ou des transients avec expiration ; mettez autoload sur ‘no’ si le plugin le permet. Ne modifiez pas directement sans sauvegardes et plan de rollback.
Task 12: Check WP-Cron health and backlog with WP-CLI
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp cron event list --fields=hook,next_run,recurrence --format=table | head
+------------------------------+---------------------+------------+
| hook | next_run | recurrence |
+------------------------------+---------------------+------------+
| wp_version_check | 2025-12-27 10:30:00 | twice_daily|
| woocommerce_cleanup_sessions | 2025-12-27 10:15:00 | hourly |
| some_plugin_heavy_job | 2025-12-27 10:01:00 | every_min |
+------------------------------+---------------------+------------+
Ce que cela signifie : Un job réglé « every_min » est suspect en contexte WordPress. Il peut se chevaucher s’il prend plus d’une minute.
Décision : Si un hook est trop fréquent ou lourd, déplacez-le vers un vrai cron, réduisez la fréquence, ou refactorez en lots.
Task 13: Reproduce the slow endpoint with curl and timing
cr0x@server:~$ curl -s -o /dev/null -w "code=%{http_code} t_total=%{time_total} t_connect=%{time_connect} t_starttransfer=%{time_starttransfer}\n" https://example.com/wp-admin/admin-ajax.php?action=some_action
code=504 t_total=60.012 t_connect=0.012 t_starttransfer=60.001
Ce que cela signifie : Un cutoff net à 60 secondes correspond au fastcgi_read_timeout de Nginx ou au timeout du proxy. Le backend a probablement continué ou a été tué.
Décision : Vérifiez les logs d’erreur Nginx/Apache autour de ce créneau, puis les logs PHP-FPM pour terminaison, puis les logs applicatifs pour savoir ce qu’il faisait.
Task 14: Inspect Nginx error log for upstream timeouts
cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/27 10:16:02 [error] 2281#2281: *991 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "POST /wp-admin/admin-ajax.php HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "example.com"
Ce que cela signifie : Nginx a arrêté d’attendre PHP-FPM. Pas nécessairement que PHP est mort — juste que le proxy a cessé d’attendre.
Décision : Si cet endpoint est légitimement long, repensez-le (async/en arrière-plan). Sinon, trouvez le goulet dans PHP/BD.
Task 15: Confirm PHP-FPM termination settings
cr0x@server:~$ sudo grep -R "request_terminate_timeout" -n /etc/php/8.2/fpm/pool.d /etc/php/8.2/fpm/php-fpm.conf
/etc/php/8.2/fpm/pool.d/www.conf:392:request_terminate_timeout = 60s
Ce que cela signifie : PHP-FPM tuera les requêtes à 60s même si max_execution_time de PHP est plus élevé.
Décision : Alignez. Si vous gardez 60s côté FPM, gardez PHP inférieur ou égal. Préférez réduire et déplacer les tâches longues hors bande.
Task 16: Check if opcode cache is enabled (performance sanity)
cr0x@server:~$ php -i | grep -E '^opcache.enable|^opcache.memory_consumption'
opcache.enable => On => On
opcache.memory_consumption => 128 => 128
Ce que cela signifie : Opcache est activé (bien). S’il était désactivé, PHP recompilerait les scripts constamment, augmentant la latence et les timeouts sous charge.
Décision : Si opcache est désactivé en FPM, activez-le. C’est l’un des rares ajustements de performance qui est à la fois ennuyeux et correct.
Correctifs sûrs qui n’enflamment pas d’autres problèmes
Il y a deux catégories de correctifs :
- Augmenter les limites (timeouts, mémoire) pour arrêter les erreurs immédiates.
- Rendre le travail plus rapide ou plus petit pour qu’il rentre dans des limites raisonnables.
Augmenter les limites est parfois nécessaire, notamment pour des importations ou des sauvegardes. Mais pour les chargements de pages courants et les clics admin, c’est souvent un pansement. Et les pansements servent aux coupures, pas aux hémorragies.
Fix 1: Increase max execution time (only with intent)
Si vous avez vraiment une requête longue légitime (import massif, génération de rapport lourde), vous pouvez augmenter max_execution_time. Faites-le au bon endroit :
- PHP-FPM : éditez
/etc/php/8.2/fpm/php.iniou des overrides de pool. - Apache mod_php : éditez
/etc/php/8.2/apache2/php.ini. - CLI WP-CLI : éditez
/etc/php/8.2/cli/php.ini(différent du web).
Puis reload des services. Ne redémarrez pas tout le serveur sauf si vous aimez les rituels.
Fix 2: Align the stack timeouts (proxy/web/FPM/PHP)
Des timeouts mal alignés créent des échecs fantômes : le client voit un 504 pendant que PHP continue, ou PHP est tué alors que Nginx attend toujours patiemment.
Une alignement pratique pour WordPress typique :
- Requêtes utilisateurs : caper à 30–60 secondes (souvent moins).
- Actions admin : 60–120 secondes si nécessaire, mais évitez autant que possible.
- Imports/exports/sauvegardes : ne les faites pas via une requête web ; utilisez WP-CLI ou des jobs asynchrones.
Quand vous augmentez les timeouts, faites-le de façon ciblée. Utilisez des blocs location pour les endpoints d’import si besoin. N’augmentez pas globalement les timeouts pour tout et ensuite vous étonnez d’avoir une pool de workers remplie de zombies.
Fix 3: Move heavy admin work to WP-CLI
Les requêtes web sont un mauvais endroit pour faire du travail lourd. Elles sont pilotées par l’utilisateur, fragiles et limitées par les proxies. WP-CLI vous permet des opérations longues sans timeouts proxy, et il est plus simple à logger.
Exemples : mises à jour de plugin, search/replace DB, gros imports, rebuilds de cache.
Fix 4: Replace “WP-Cron via traffic” with real cron
C’est l’une des meilleures améliorations de fiabilité que vous puissiez faire.
- Désactivez le déclenchement WP-Cron à chaque requête.
- Exécutez
wp cron event run --due-nowdepuis le cron système chaque minute ou toutes les cinq minutes.
Ça stabilise le timing d’exécution des jobs et réduit les pics surprises pendant les chargements de page.
Fix 5: Fix autoload bloat
Le bloat autoload est une latence auto-infligée. Approche sûre :
- Mesurez la taille totale autoload.
- Identifiez les plus gros responsables.
- Pour chaque responsable : déterminez s’il est sûr de mettre autoload sur ‘no’ ou de migrer vers des transients/cache d’objets.
- Testez en staging. Déployez avec rollback.
Fix 6: Fix slow queries and add indexes (carefully)
Les indexes ne sont pas magiques ; ce sont des compromis. Mais des indexes manquants sur des tables de plugins sont une cause récurrente de timeouts.
N’ajoutez pas d’index à l’aveugle en production pendant le pic de trafic. Ajouter un index peut verrouiller des tables ou au moins augmenter l’I/O pendant la construction. Planifiez-le.
Fix 7: Right-size PHP-FPM workers (pm settings)
Plus de workers n’est pas toujours plus de débit. Trop de workers peuvent :
- épuiser la mémoire et provoquer du swap (lent, puis plus lent),
- augmenter les connexions BD et la contention,
- augmenter la contention I/O disque.
Commencez par la réalité : coeurs CPU, mémoire, coût moyen d’une requête. Puis réglez pm.max_children sur un nombre réellement exploitable sans swap.
Fix 8: Address storage and filesystem realities
En tant qu’ingénieur stockage dans la pièce, voici la partie que les gens sautent parce que ce n’est pas un réglage WordPress :
- Si la latence disque est élevée, optimisez votre stockage. Aucun réglage PHP ne corrige des disques lents.
- Vérifiez les conditions de disque plein : quand les disques se remplissent, tout devient étrange.
- Les volumes réseau (certains disques cloud bon marché) peuvent avoir une latence imprévisible sous limites de burst.
Petite blague #2 : Mettre max_execution_time à 0, c’est comme enlever les ceintures parce qu’elles froissent vos vêtements.
Trois mini-récits d’entreprise issus du terrain
Mini-récit #1 : Un incident causé par une mauvaise hypothèse
La société avait une boutique WooCommerce « simple ». Pas énorme, pas minuscule. Suffisamment de chiffre d’affaires pour que les pannes déclenchent des messages Slack à tonalité particulière. L’équipe ops a reçu un flot d’échecs de checkout : clients bloqués sur une page qui tourne puis des erreurs. Les logs Nginx montraient des 504.
Le développeur de garde a supposé que c’était un timeout PHP et a monté max_execution_time de 30 à 120. Les erreurs ont empiré. Ce n’est pas inhabituel : des timeouts plus longs signifient plus de requêtes qui s’accumulent. La concurrence augmente, la pression BD augmente, et tout ralentit davantage. Le système est devenu un embouteillage au ralenti.
La mauvaise hypothèse était que « l’erreur de timeout » signifiait « PHP a besoin de plus de temps ». En réalité, PHP attendait. Le processlist MySQL a révélé plusieurs sessions bloquées sur un verrou de métadonnées. Une mise à jour de plugin avait lancé un ALTER TABLE en heures ouvrées. L’ALTER gardait les verrous suffisamment longtemps pour que les requêtes de checkout se mettent en file derrière, et les workers PHP attendaient jusqu’aux timeouts proxy.
Le correctif n’a pas été « plus de temps ». Ce fut : annuler le changement de schéma, vider la file, et planifier les changements de schéma hors production avec une approche de migration plus sûre. Ensuite, ils ont mis en place une règle : les mises à jour de plugins touchant le schéma se font en fenêtre de maintenance, validées en staging sur une copie des données de production.
Le postmortem fut clair : les timeouts étaient le symptôme. Le verrouillage était la maladie. L’équipe a mis à jour son runbook pour vérifier les verrous BD avant de toucher aux timeouts.
Mini-récit #2 : Une optimisation qui a mal tourné
Une autre organisation avait un site WordPress riche en contenu avec un cache agressif. Ils souhaitaient accélérer l’admin, alors ils ont réglé PHP-FPM pour « débit maximal » : augmenté fortement pm.max_children et réduit les timeouts pour « échouer vite ». Le benchmark sur une machine de staging calme était excellent.
La production n’était pas d’accord. Pendant les heures éditoriales de pointe, beaucoup d’admins téléchargeaient des images et éditaient des articles pendant que le trafic continuait. L’I/O disque a explosé car le traitement d’images et les écritures de cache sont intensifs en écriture. Avec trop de workers PHP-FPM, la longueur de la file disque a augmenté, l’iowait a grimpé, et chaque requête a ralenti. Les requêtes ont commencé à timeout — pas parce qu’une requête individuelle était lourde, mais parce que le système était sursaturé.
L’optimisation a échoué car elle supposait que le goulet était CPU. Il ne l’était pas. Le stockage était le goulet, et multiplier les workers multipliait la contention. L’équipe avait construit un amplificateur de concurrence sur un disque lent.
Le correctif fut contre-intuitif : réduire la concurrence PHP-FPM pour correspondre à la capacité I/O, activer correctement opcache, et déplacer le traitement d’images vers une file asynchrone. Ils ont aussi ajouté la surveillance de la latence disque comme signal SLO prioritaire au lieu de ne regarder que CPU et mémoire.
Après cela, les timeouts ont majoritairement disparu. Non pas parce qu’ils ont permis plus de temps, mais parce qu’ils ont arrêté de démarrer plus de travail que le stockage ne pouvait terminer.
Mini-récit #3 : Une pratique ennuyeuse mais correcte qui a sauvé la mise
Une équipe marketing SaaS utilisait WordPress comme porte d’entrée. Ils n’étaient pas « techniques », mais ils avaient un SRE qui insistait sur des habitudes ennuyeuses : rétention des logs, dashboards, un environnement staging miroir de la production, et une « journée de mise à jour » mensuelle avec répétitions de rollback.
Un mardi, une mise à jour de plugin a introduit une régression : un nouveau widget dashboard faisait un appel API externe lent à chaque chargement de page admin. Les éditeurs ont commencé à voir des erreurs de temps d’exécution maximal. Cela aurait pu devenir une bagarre d’une journée.
Mais leurs pratiques ennuyeuses ont fonctionné. Le SRE a comparé les graphes avant/après : la latence HTTP sortante a grimpé au moment exact de la mise à jour du plugin. Les logs PHP montraient le timeout provenant des fonctions HTTP de WordPress. Ils ont rollbacké le plugin via leur procédure documentée (réelle, pas « on espère une sauvegarde »). Les erreurs ont chuté immédiatement.
Puis ils ont redéployé avec une mitigation : désactivation du widget dashboard, ajout d’un timeout externe court et mise en cache, et pression sur le fournisseur pour corriger. L’histoire se termine comme chaque ops veut : « Les clients ont à peine remarqué. »
La pratique ennuyeuse n’était pas magique. C’était avoir de l’observabilité et un rollback avant d’en avoir besoin.
Erreurs fréquentes : symptôme → cause racine → correctif
Voici des motifs que je vois régulièrement. Si vous en reconnaissez un, sautez le débogage héroïque et allez directement vers la cause probable.
1) Symptom: 504 Gateway Timeout in browser, no PHP fatal shown
Cause racine : Le timeout Nginx/Apache/proxy est inférieur à PHP/PHP-FPM ou le backend est bloqué et le proxy abandonne avant.
Fix : Vérifiez le log Nginx pour « upstream timed out ». Alignez fastcgi_read_timeout avec les paramètres de terminaison PHP-FPM. Puis trouvez pourquoi le backend est lent (verrous BD, disque, CPU) au lieu d’augmenter seulement le timeout du proxy.
2) Symptom: “Maximum execution time of 30 seconds exceeded” during plugin updates
Cause racine : L’updater de plugin effectue des écritures sur le système de fichiers lentement (permissions, disque lent), appels distants, ou décompression sous charge.
Fix : Exécutez les mises à jour via WP-CLI en période de faible trafic. Assurez-vous que la propriété des fichiers est correcte pour éviter des retries de permission. Vérifiez la latence du stockage.
3) Symptom: Admin area slow, front end “mostly fine”
Cause racine : Les pages admin déclenchent plus de requêtes, chargent plus de plugins, appellent des API externes, ou génèrent des rapports. Elles touchent aussi plus souvent le bloat autoload et des hooks lourds.
Fix : Profilez les endpoints admin ; auditez les plugins qui ajoutent des widgets dashboard ; réduisez les options autoloadées ; désactivez les fonctionnalités admin inutiles en production.
4) Symptom: Errors cluster during imports or media uploads
Cause racine : Le traitement d’images (GD/Imagick) est lourd CPU+I/O ; les imports effectuent de larges inserts BD et peuvent provoquer des verrous.
Fix : Utilisez WP-CLI pour les imports ; opérez par lots ; externalisez le traitement média ou générez les miniatures de façon asynchrone. Augmentez la mémoire plutôt que le temps si vous thrash CPU à cause d’OOM.
5) Symptom: Random timeouts, especially at low traffic
Cause racine : Accumulation WP-Cron : les tâches ne s’exécutent pas régulièrement, puis une requête unique déclenche plusieurs tâches en retard.
Fix : Désactivez WP-Cron et planifiez un vrai cron. Vérifiez les jobs qui se chevauchent et réduisez la fréquence.
6) Symptom: After “increasing performance,” timeouts got worse
Cause racine : Augmentation des workers PHP-FPM ou des écritures de cache qui ont amplifié la contention (BD, disque). Ou une optimisation a supprimé le cache pour les endpoints admin, augmentant la charge.
Fix : Revenez à une configuration connue bonne. Redimensionnez la concurrence. Mesurez iowait et les lock waits BD. Changez une chose à la fois.
7) Symptom: Timeouts only on one specific page/action
Cause racine : Un endpoint plugin unique effectue un travail coûteux (appels distants, requête lourde, scan complet de table).
Fix : Désactivez le plugin pour confirmer ; ensuite corrigez ou remplacez-le. Ajoutez des indexes si nécessaire. Déplacez le travail en asynchrone.
Listes de contrôle / plan pas-à-pas
Checklist A: Get the site stable in 15 minutes (triage)
- Confirmer le mode de défaillance : fatal PHP vs 504 vs 502. Utilisez les logs, pas l’intuition.
- Vérifier la pression sur les ressources :
topetiostat. Si iowait est élevé, ne touchez pas aux timeouts. - Vérifier les verrous BD :
SHOW PROCESSLIST. Si vous voyez des locks metadata, arrêtez les changements de schéma et libérez les bloqueurs. - Identifier l’endpoint : Quelle URL/action le déclenche ? Reproduisez avec
curl -wtiming. - Mitiger : désactivez le plugin ou la fonctionnalité fautive si c’est clairement la cause ; ou routez les tâches lourdes vers WP-CLI.
Checklist B: Make the fix durable (next day work)
- Aligner les timeouts entre les couches : proxy/web/FPM/PHP. Documentez.
- Corriger le bloat autoload : mesurer, identifier les responsables, remédier avec des tests.
- Corriger les requêtes lentes : activer le slow query log temporairement ; ajouter des indexes quand justifié.
- Implémenter un vrai cron : désactiver WP-Cron et le planifier correctement.
- Redimensionner PHP-FPM : définir un
pm.max_childrensensé basé sur mémoire et CPU. - Établir une pipeline de mise à jour sûre : validation en staging, fenêtres de maintenance, procédure de rollback.
Checklist C: When raising timeouts is acceptable
- Grosses importations ponctuelles que vous contrôlez (privilégiez WP-CLI).
- Rapports admin uniquement avec concurrence limitée, où vous maîtrisez aussi l’accès.
- Sauvegardes/exports exécutés via CLI ou job asynchrone, pas via requête web.
Si vous augmentez les timeouts pour des pages routinières ou le checkout, vous traitez probablement le symptôme.
Faits intéressants et contexte (l’histoire compte)
- La limite d’exécution PHP existe depuis des décennies et a été conçue pour protéger les serveurs mutualisés contre les scripts incontrôlés.
- Le « cron » de WordPress n’est pas un cron système ; c’est un pseudo-cron déclenché par le trafic web, astucieux jusqu’à un certain point.
- Le classique 30 secondes par défaut est courant parce que de nombreux serveurs web historiques supposaient « un humain n’attendra pas plus longtemps », et parce que cela empêche le pinning de ressources de type slow-loris.
- Les piles de timeouts sont calées par conception : chaque couche se protège indépendamment. C’est pourquoi corriger un timeout révèle souvent le suivant.
- Les options autoloadées sont chargées à chaque requête, et la table peut devenir un goulet de performance si des plugins y stockent de gros blobs.
- Beaucoup d’actions admin WordPress sont synchrones (update, install, import), ce qui est opérationnellement délicat en production.
- PHP-FPM a ajouté des contrôles au niveau du pool (comme les timeouts de terminaison) en partie pour éviter qu’un pool monopolise un serveur.
- Les verrous de métadonnées BD sont devenus plus visibles à mesure que les sites ont grandi et que le « juste lancer ALTER TABLE » a heurté les attentes de trafic continu.
- Le cache d’objets a évolué comme palliatif au chargement répété d’options et de requêtes, mais il peut masquer un bloat sous-jacent si vous ne mesurez pas.
FAQ
1) Should I just increase max_execution_time to 300?
Augmentez-le seulement si vous pouvez expliquer quel travail exige 300 secondes et pourquoi il doit se faire dans une requête web. Pour des chargements normaux, considérez cela comme un bug à corriger, pas une limite à relever.
2) Why does it happen only when updating plugins/themes?
Les mises à jour impliquent téléchargement, vérification, décompression et écriture de nombreux fichiers. Sur disques lents ou CPU contraint, cela peut dépasser 30–60 secondes. Exécutez les mises à jour via WP-CLI en période de faible trafic et confirmez que les permissions du système de fichiers sont correctes.
3) I increased max_execution_time but I still get 504 errors. Why?
Parce que le timeout de votre proxy/serveur web (Nginx fastcgi_read_timeout, Apache Timeout, timeout du load balancer) est probablement inférieur à celui de PHP. Alignez-les, puis corrigez la lenteur sous-jacente.
4) What’s the difference between max_execution_time and request_terminate_timeout?
max_execution_time est appliqué par PHP. request_terminate_timeout est appliqué par PHP-FPM et peut tuer une requête indépendamment du réglage PHP. S’ils ne sont pas d’accord, le plus petit l’emporte en pratique.
5) Can a database problem cause a PHP execution time error?
Oui. Le « temps d’exécution » PHP inclut le temps passé à attendre la base de données. Une requête lente ou un verrou peut consommer tout le budget alors que PHP est effectivement inactif.
6) Does increasing PHP memory limit help with execution time errors?
Parfois. Si la requête est lente parce qu’elle thrash la mémoire ou provoque du swap, plus de mémoire peut réduire le temps mur. Mais si la lenteur vient de verrous BD ou de la latence disque, la mémoire ne vous sauvera pas.
7) How do I know if WP-Cron is involved?
Cherchez des timeouts sur des chargements normaux qui coïncident avec des hooks cron, des événements en retard, ou des hooks personnalisés fréquents. Utilisez WP-CLI pour lister les événements et repérer les jobs lourds. Si des événements en retard s’accumulent, passez au cron système.
8) Is it safe to disable a plugin to fix timeouts?
Pendant un incident, oui — si vous comprenez l’impact business. Désactivez le plugin qui déclenche le timeout pour restaurer le service, puis investiguez. Pour WooCommerce/plugins de paiement, prudence : désactiver peut casser le checkout. Préférez des feature flags ou des désactivations ciblées.
9) Why does it happen on shared hosting more often?
L’hébergement mutualisé impose souvent des limites strictes, des voisins bruyants, un stockage plus lent, et moins de contrôle sur PHP-FPM et les paramètres proxy. Vous êtes aussi plus susceptible d’avoir des valeurs par défaut basses et une observabilité limitée.
10) What’s the most “SRE-correct” fix for long tasks?
Rendez-les asynchrones : mettez le travail en file, renvoyez une réponse rapidement, et traitez en arrière-plan avec retries, timeouts et idempotence. WordPress le fait imparfaitement, mais c’est toujours mieux que de bloquer des workers web.
Conclusion : prochaines étapes à accomplir réellement
Si vous retenez une chose : les erreurs de temps d’exécution sont généralement le serveur qui vous dit « cette requête est trop grosse, ou le système est trop lent ». Le chemin sûr est de prouver lequel.
- Classifiez le timeout (PHP vs proxy vs FPM) en utilisant les logs.
- Exécutez le diagnostic rapide : CPU/iowait, verrous BD, reproduction de l’endpoint.
- Corrigez le goulet (bloat autoload, requêtes lentes, latence stockage, FPM sursaturé) avant d’augmenter les limites.
- Déplacez le travail lourd hors des requêtes web : WP-CLI, vrai cron, jobs en arrière-plan.
- Alignez les timeouts intentionnellement, documentez-les, et gardez-les serrés pour les chemins orientés utilisateur.
Faites cela, et « Max execution time exceeded » redeviendra ce qu’il devrait être : un garde-fou que vous rencontrez rarement, pas un mode de vie.