Table des matières :
- 503 Service Unavailable : sémantique HTTP, Retry-After et pièges courants
- Identifier qui renvoie le 503 : CDN/WAF, reverse proxy, web server, PHP-FPM, application
- Logs à corréler : Nginx/Apache, PHP-FPM, PrestaShop, MySQL et systemd
- Ressources et quotas : CPU, RAM, I/O, file descriptors, timeouts et dimensionnement PHP-FPM
- Remédiation durable : stabiliser la capacité (cache, rate limiting), dégrader proprement, monitorer avant l’incident
503 Service Unavailable : sémantique HTTP, Retry-After et pièges courants
Une erreur HTTP 503 n’est pas un « bug PrestaShop ». C’est un code de statut qui dit uniquement : le serveur (ou un composant intermédiaire) ne peut pas traiter la requête maintenant. La définition normative est claire : « The 503 (Service Unavailable) status code indicates that the server is currently unable to handle the request due to a temporary overload or scheduled maintenance » (IETF, RFC 9110 – HTTP Semantics, section 15.6.4). En pratique, ça couvre autant un pic de charge qu’un backend hors-ligne, un pool PHP-FPM saturé, ou un Ingress Kubernetes sans endpoints.
Le 503 est souvent confondu avec 502/504. 502 (Bad Gateway) indique typiquement un proxy qui reçoit une réponse invalide d’un upstream. 504 (Gateway Timeout) indique un timeout entre proxy et upstream. 503 est plus « intentionnel » (maintenance, overload), mais il est aussi retourné par certains reverse proxies quand l’upstream n’est pas disponible (selon la config). Ne partez donc pas du principe que « 503 = maintenance ». Dans un stack Nginx → PHP-FPM, un 503 pur est moins fréquent qu’un 502/504 ; dans un stack HAProxy/Apache/mod_proxy, c’est l’inverse.
Pour fixer les idées, une lecture utile (sans remplacer les RFC) est la doc MDN sur les codes HTTP (503).
Un repère simple quand vous êtes en incident (ou en post-mortem) :
| Code | Où ça casse le plus souvent | Signal implicite |
|---|---|---|
| 502 | Proxy/reverse proxy → upstream | réponse « invalide » / reset / backend qui parle mal |
| 504 | Proxy/reverse proxy → upstream | upstream trop lent / timeout réseau / pool saturé |
| 503 | Webserver/proxy/app | surcharge temporaire, maintenance, ou aucun backend dispo |
Le code 503 a un attribut utile : l’en-tête Retry-After (secondes ou date HTTP) pour indiquer quand retenter. Exemple (secondes) :
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
Deux pièges courants côté production :
- 503 mis en cache par erreur : certains CDN/proxies peuvent mettre en cache des réponses d’erreur si vous ne contrôlez pas explicitement les headers. Pour une maintenance « volontaire », utilisez au minimum
Cache-Control: no-store(ou une durée très courte), sinon vous pouvez prolonger artificiellement l’indisponibilité même après retour du backend. - maintenance en 200 : si votre page « maintenance » répond 200, vous créez un faux positif côté monitoring et un signal SEO ambigu (les crawlers voient du contenu « normal »). Google recommande explicitement l’usage du 503 pendant une indisponibilité temporaire, en soulignant qu’il « reviendra plus tard » (Google Search Central, Temporary site outages).
Enfin, note pratique : pour des boutiques françaises/européennes, les incidents surviennent souvent à des moments très « calendaires » (lancements, campagnes TV, soldes, Black Friday). Avoir un 503 « propre » avec Retry-After et une page explicite (et légère) évite que les navigateurs et robots transforment une surcharge temporaire en problème durable d’indexation et de cache.
Identifier qui renvoie le 503 : CDN/WAF, reverse proxy, web server, PHP-FPM, application
Avant de lire des logs au hasard, il faut localiser l’émetteur du 503. Commencez par inspecter la réponse brute depuis l’extérieur :
curl -svI https://votre-domaine.tld/ 2>&1 | sed -n '1,25p'
Cherchez les headers qui trahissent la couche (ex. server: cloudflare, via: 1.1 varnish, x-served-by, x-cache, x-request-id, x-varnish). Un 503 Cloudflare (« origin unreachable », « overloaded ») n’a pas la même remédiation qu’un 503 Nginx/HAProxy.
Quelques indices rapides (à recouper, pas à prendre comme vérité absolue) :
- CDN/WAF : présence de headers spécifiques (
cf-ray,akamai-*,x-sucuri-*), HTML « générique » du fournisseur, certificat TLS du CDN, IP de destination appartenant au réseau du CDN. - Reverse proxy interne (HAProxy/Nginx/Varnish) :
via,x-varnish,x-cache, ou un body d’erreur « minimaliste » typique du proxy. - Web server :
server: nginx/apache, pages d’erreur configurées localement, timings faibles (réponse quasi immédiate) si aucun upstream n’est contacté. - Applicatif/PHP : réponse plus lente, parfois un body HTML « site » (header/footer), cookies applicatifs posés avant l’erreur, ou des traces dans les logs applicatifs.
Si vous avez un reverse proxy interne (HAProxy, Nginx, Varnish), faites le même curl depuis le serveur proxy vers l’upstream pour voir si la panne est en amont. Et si vous êtes derrière un CDN, pensez au test « bypass » (quand c’est possible et autorisé) : résoudre le domaine vers l’IP d’origine.
Exemple avec --resolve (pratique pour isoler CDN vs origin) :
# Remplacez 203.0.113.10 par l'IP d'origine autorisée, et testez SANS modifier le DNS
curl -svI --resolve votre-domaine.tld:443:203.0.113.10 https://votre-domaine.tld/
Côté PrestaShop (8.x/9.x), il existe un cas trivial : mode maintenance / boutique désactivée. Le core peut renvoyer une page de maintenance avec un 503 (selon la version et le contrôleur). Vérifiez rapidement : statut « Activer la boutique », IP de maintenance, et la valeur PS_SHOP_ENABLE en base. Si vous êtes en train de déployer et que votre pipeline « coupe » la boutique, vous pouvez générer un 503 « propre » (avec Retry-After) plutôt que de laisser un pool PHP-FPM s’écrouler.
Enfin, n’oubliez pas que beaucoup de 503 « applicatifs » sont en réalité des 503 proxy dus à un backend marqué DOWN. Sur HAProxy, un frontend peut renvoyer 503 s’il n’a aucun serveur disponible dans le backend (c’est un pattern classique quand les checks échouent). Si vous utilisez HAProxy, relisez vos paramètres de health-checks, timeouts et graceful shutdown (voir aussi : HAProxy reverse proxy : terminaison TLS, rate limiting et supervision Prometheus et HAProxy 3.2 : déploiement systemd, configuration frontend/backend, ACL).
Logs à corréler : Nginx/Apache, PHP-FPM, PrestaShop, MySQL et systemd
Prérequis (à expliciter parce que ça évite des conneries en prod) : accès SSH, droits de lecture sur /var/log, et idéalement un horodatage synchronisé (NTP/chrony). Sans timestamps alignés, votre corrélation est du bruit.
Astuce très concrète en Europe (et typiquement en France) : si vos serveurs journalisent en heure locale, les changements CET/CEST (heure d’été/hiver) peuvent compliquer les post-mortems. Le plus robuste est de consigner en UTC côté serveurs et d’afficher en local dans vos dashboards.
Sur Debian/Ubuntu, commencez par le journal systemd :
sudo journalctl -u nginx -S "-15min" --no-pager
sudo journalctl -u php8.2-fpm -S "-15min" --no-pager
sudo journalctl -u mysql -S "-15min" --no-pager
Sur Nginx, le 503 se voit surtout dans error.log (upstream failed, connect refused, no live upstreams). Exemple typique (indicatif) :
upstream prematurely closed connection while reading response header from upstream
no live upstreams while connecting to upstream
connect() failed (111: Connection refused) while connecting to upstream
Mais pour trancher « 503 généré par Nginx » vs « 503 renvoyé par l’upstream », l’access log est souvent plus parlant si vous logguez upstream_status et les temps. Exemple de format (à adapter) :
log_format timed '$remote_addr - $host [$time_local] "$request" '
'$status $body_bytes_sent '
'rt=$request_time urt=$upstream_response_time '
'ust=$upstream_status uaddr=$upstream_addr '
'rid=$request_id';
access_log /var/log/nginx/access.log timed;
- Si
status=503etust=-(vide), Nginx a probablement répondu sans joindre d’upstream (règle, maintenance, limit, etc.). - Si
status=503etust=503, l’upstream (ou le proxy amont) a renvoyé lui-même un 503. - Si
urtest élevé et finit en 504/502, vous êtes plus sur un problème de latence/saturation que sur une maintenance.
Sur Apache + proxy_fcgi, cherchez AH01079, AH01114 et les erreurs de backend indisponible. L’objectif est de déterminer si le serveur web renvoie 503 parce que PHP-FPM ne répond pas, ou parce que l’application renvoie 503 elle-même.
Sur PHP-FPM (PHP 8.2+ ; PrestaShop 9.1 supporte un spectre large, voir PrestaShop 9.1 : compatibilité PHP 8.1–8.5, CLI et nouveautés développeurs), les symptômes « 503 » sont rarement explicitement nommés. Vous allez plutôt voir :
server reached pm.max_children(pool saturé → requêtes en file → timeouts en amont)child exited on signal 9 (SIGKILL)(souvent OOM)pool ... seems busy(selon versions/config)slowlogsi activé (stack trace PHP)
Activez un slowlog FPM sur un pool dédié (attention : en prod, ça écrit beaucoup si vous mettez un seuil trop bas) :
; /etc/php/8.2/fpm/pool.d/www.conf
request_slowlog_timeout = 5s
slowlog = /var/log/php8.2-fpm/www-slow.log
Côté PrestaShop, ne vous contentez pas du mode debug. Selon la version, vous avez aussi des logs applicatifs dans var/logs/ (par ex. prod.log/dev.log pour les composants Symfony) et des logs modules (parfois verbeux) : ils sont précieux quand un module provoque des requêtes longues ou des boucles de hooks.
Les 503 de charge viennent souvent de requêtes SQL lentes, de hooks coûteux ou de concurrence sur des verrous. Activez le slow query log MySQL/MariaDB et corrélez avec la minute exacte de l’incident : Requêtes MySQL lentes PrestaShop : activer slow query log. Un 503 « backend down » peut être une conséquence indirecte d’un MySQL qui swap, qui atteint max_connections, ou qui accumule des verrous (ex. pics d’écriture panier/stock).
Checklist de corrélation (simple, mais efficace) :
- même fenêtre temporelle (UTC idéalement) sur Nginx/Apache, PHP-FPM, MySQL ;
- identifier une URL « lourde » (facettes, recherche, panier, checkout) ;
- vérifier si le 503 touche tout le site ou seulement certains endpoints ;
- isoler « saturation » (temps qui montent) vs « rupture » (refus connexions / service down).
Ressources et quotas : CPU, RAM, I/O, file descriptors, timeouts et dimensionnement PHP-FPM
La majorité des 503 en e-commerce sont des pannes de capacité déguisées : CPU saturé, RAM insuffisante, I/O qui part en latence, limite de processus ou de descripteurs de fichiers, etc. Brendan Gregg résume bien une méthode de triage applicable ici : « For every resource, check: utilization, saturation, and errors » (Brendan Gregg, USE Method, http://www.brendangregg.com/usemethod.html). Traduction opérationnelle : vous mesurez l’utilisation (CPU%), la saturation (run queue, iowait, backlog) et les erreurs (OOM, resets TCP, erreurs disque).
Commandes de base (Linux) à lancer pendant ou juste après l’incident :
uptime; top -b -n1 | head -n 20
free -h
vmstat 1 5
iostat -xz 1 5 # paquet sysstat
ss -s
sudo dmesg -T | egrep -i "oom|killed process|ext4|nvme|xfs" | tail -n 50
Si vous voyez des OOM-kills, ce n’est pas « un 503 », c’est un process tué, donc un effet domino. Dans ce cas, le remède est structurel : augmenter la RAM, réduire la pression mémoire (OPcache, modules, workers), ou imposer des limites cgroup (containers) pour isoler.
Pour PHP-FPM, le dimensionnement de pm.max_children doit être basé sur une mesure : taille RSS moyenne d’un worker en charge.
ps --no-headers -o rss -C php-fpm8.2 | awk '{sum+=$1; n++} END{print (n?sum/n:0)" KB"}'
Exemple concret (VPS 8 Go, Nginx + MySQL + Redis) : si un worker PHP-FPM consomme ~90 Mo RSS en moyenne sous PrestaShop (ce n’est pas rare avec beaucoup de modules), et que vous gardez 2 Go pour MySQL + OS, vous avez ~6 Go pour FPM → 6000/90 ≈ 66 workers maximum théorique. En pratique vous mettez moins (marge, pics, fragmentation) et vous contrôlez la concurrence via cache et rate limiting. Pour aller plus loin sur les goulots d’étranglement typiques PrestaShop (FPM, OPcache, MySQL), voir : Performance PrestaShop : benchmarks et optimisation PHP-FPM, OPCache, MySQL et OPcache PHP : activer et vérifier l’extension dans cPanel.
Autre zone aveugle fréquente : les timeouts incohérents entre couches. Exemple classique : HAProxy timeout 30s, Nginx 60s, PHP maxexecutiontime 120s, MySQL encore plus… Résultat : vous voyez des 503/504 « aléatoires » selon qui abandonne en premier. Sans rentrer dans un dogme, l’idée est d’aligner les timeouts sur votre SLO (latence acceptable) et de rendre la coupure prévisible.
Dernier piège fréquent (et brutal) : les descripteurs de fichiers. Une limite trop basse (ulimit -n) ou un leak de sockets peut faire tomber Nginx/PHP-FPM/DB en cascade et provoquer des 503/502 selon la couche.
Commandes utiles pour confirmer (et voir si systemd impose une limite) :
ulimit -n
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr
systemctl show nginx php8.2-fpm --property=LimitNOFILE
Sur PrestaShop 9, ce point est suffisamment fréquent pour mériter un runbook dédié : PrestaShop 9 : corriger l’erreur « too many open files ».
Remédiation durable : stabiliser la capacité (cache, rate limiting), dégrader proprement, monitorer avant l’incident
Une remédiation « durable » vise à réduire la probabilité d’atteindre la saturation, pas à masquer l’erreur. Sur PrestaShop, la voie la plus rentable (techniquement) est le cache multi-niveaux : OPcache (code), Redis/Memcached (objets/sessions selon stratégie), Varnish/HTTP cache (pages anonymes), et éventuellement ESI si votre front le supporte. Les articles suivants détaillent les implémentations et leurs limites : Cache PrestaShop : Varnish, Redis, Memcached et OPcache côté serveur, Redis PrestaShop : configurer le cache sur VPS ou serveur dédié et Varnish Cache : configuration ESI pour optimiser le cache par fragments.
Mini-scénario (réaliste) : pendant un pic de trafic, la catégorie « promo » (pages avec facettes + tri + pagination) fait exploser le volume de requêtes SQL et la concurrence PHP. Mettre en cache HTTP les pages anonymes (Varnish/CDN) réduit immédiatement le nombre d’exécutions PHP. Ensuite, mettre la recherche et/ou les facettes sur un moteur dédié évite que chaque frappe ou filtre déclenche une cascade de requêtes côté MySQL. Vous ne « cachez » pas l’erreur : vous retirez de la charge à l’endroit qui sature.
La deuxième brique, surtout en période de soldes/pics, c’est contrôler la concurrence. Si vous laissez tout passer jusqu’à l’effondrement, vous obtenez un 503 « sale » (timeouts, paniers cassés, DB instable). Un reverse proxy (HAProxy/Nginx) doit pouvoir appliquer du rate limiting et des protections ciblées (bots, navigation à facettes abusive, endpoints lourds). C’est exactement la logique décrite dans PrestaShop sécurité : bloquer la navigation à facettes via fail2ban : limiter la surface de charge inutile plutôt que d’augmenter aveuglément les ressources. Pour les environnements haute charge, relisez aussi : PrestaShop pics de trafic : architecture cloud scalable et haute disponibilité.
Troisième point : dégradation contrôlée. PrestaShop reste majoritairement synchrone (requêtes PHP bloquantes, hooks modules, panier/stock), donc en surcharge vous devez décider quoi sacrifier : recherche interne, facettes, recommandations, exports, webhooks, etc. Deux approches pragmatiques (souvent négligées) :
- débrancher temporairement les fonctionnalités non critiques (ex. recommandations, blocs dynamiques) si elles déclenchent des calls externes ou des requêtes lourdes ;
- réduire l’explosion combinatoire (facettes trop nombreuses, tri multiples) plutôt que d’ajouter uniquement du hardware.
Mettre la recherche sur un service dédié (Meilisearch/Elasticsearch) peut sortir une charge significative du PHP/SQL (voir Recherche interne PrestaShop : intégrer Meilisearch pour booster la conversion et PrestaShop ElasticSearch : accélérer la recherche produit sur grands catalogues). Et si vous devez mettre le site en maintenance, faites-le correctement : 503 + Retry-After, whitelist IP, et monitoring qui sait que c’est volontaire.
Enfin, si vous découvrez un 503 via un client, vous êtes déjà en retard. Mettez en place une observabilité minimale : métriques (CPU, RAM, iowait, FPM queue, MySQL connections), logs centralisés, et alertes basées sur des symptômes actionnables (latence 95p, taux 5xx). Les stacks simples type Netdata + Grafana/Prometheus suffisent souvent pour voir venir la saturation : Netdata monitoring : surveiller AWS, Kubernetes, bases de données et serveurs web, Grafana sur Ubuntu : installation APT et configuration initiale et Surveillance PrestaShop : tableau de bord, seuils et réduction des fausses alertes.
Une règle simple pour rendre les alertes utiles : évitez « 503 détecté » seul, et déclenchez sur un couple symptôme + cause probable, par exemple :
- 5xx en hausse et
pm.max_children reacheddans les logs FPM - latence 95p en hausse et
Threads_running/verrous MySQL en hausse - 503 proxy et health-check HAProxy en échec sur tous les backends
Une fois instrumenté, un 503 n’est plus un mystère : c’est un point sur une courbe (queue FPM, iowait, max_connections) avec une cause assignable et une correction testable.
