Ubuntu 24.04 : limites d’upload PHP — corriger upload_max_filesize là où ça compte (cas n°10)

Cet article vous a aidé ?

Quelqu’un tente d’uploader un PDF de 40 Mo. L’interface tourne en rond, puis meurt avec un satisfait « 413 » ou un vague « The uploaded file exceeds the upload_max_filesize directive. » Vous augmentez upload_max_filesize à 128M, vous rechargez quelque chose, et… rien ne change. Classique.

Sur Ubuntu 24.04, le problème n’est que rarement « PHP a une limite. » Le problème est « vous avez modifié la mauvaise limite, au mauvais endroit, pour le mauvais SAPI, derrière un proxy qui n’a pas été informé. » Corrigeons là où ça compte réellement.

Le modèle mental : les uploads sont une course de relais

L’envoi de fichier n’est pas « une histoire PHP. » C’est une requête HTTP avec un corps. Ce corps traverse une chaîne :

  1. Le client (navigateur/application mobile) crée une requête multipart/form-data.
  2. Toute edge/WAF/CDN/reverse proxy applique sa propre taille maximale de requête et ses timeouts.
  3. Votre load balancer ou ingress fait de même.
  4. Votre serveur web (Nginx/Apache) décide de bufferiser, streamer ou refuser le corps.
  5. PHP-FPM reçoit la requête, écrit des fichiers temporaires d’upload, puis l’appli les lit/déplace.
  6. Votre appli/framework peut imposer sa propre limite de validation et la refuser de toute façon.
  7. Le stockage (quota disque, permissions, système de fichiers plein) peut saboter l’étape du fichier temporaire.

La limite que vous devez changer est la première dans la chaîne qui est inférieure à votre objectif. Changer une limite plus en aval revient à élargir une bretelle alors que le pont juste avant est encore en une seule voie.

Blague n°1 : Augmenter upload_max_filesize sans vérifier Nginx, c’est comme acheter une valise plus grande pour un vol avec uniquement un bagage à main. Vous rencontrerez quand même l’agent à la porte d’embarquement.

Faits intéressants et contexte historique

  • Les limites d’upload de PHP ont été conçues pour l’hébergement mutualisé. Les premières déploiements supposaient de nombreux utilisateurs non liés sur une même machine ; des valeurs conservatrices évitaient qu’un upload « tue » la machine.
  • post_max_size existe parce que les uploads font partie du corps des POST. Les uploads multipart sont toujours des données POST ; PHP compte tout le corps de la requête, pas seulement le fichier.
  • Nginx a historiquement par défaut une taille de corps de requête de 1 Mo. Ce défaut a ruiné plus de mises en production un vendredi que la plupart des gens n’osent l’admettre.
  • Apache a plusieurs réglages selon les modules. L’histoire des limites de corps de requête change selon les paramètres core, mod_security et les modules proxy.
  • 413 n’est pas une « erreur PHP. » C’est un statut HTTP signifiant que le serveur (ou proxy) refuse l’entité/payload de la requête ; PHP n’a peut‑être même pas vu la requête.
  • Les uploads PHP touchent le disque avant que votre code ne les voie. Le fichier atterrit dans upload_tmp_dir (ou le tmp système) d’abord ; si ce système de fichiers est plein, vous obtenez des échecs mystérieux.
  • PHP-FPM a introduit des overrides au niveau des pools pour le multi‑tenant. C’est utile jusqu’à ce que vous oubliez qu’un pool a php_admin_value qui écrase silencieusement les ini.
  • systemd a changé les habitudes de « reload » de beaucoup d’admins. Sur les Ubuntu modernes, « restart le daemon » est fiable ; « reload la config » dépend du service et de votre patience.
  • Les navigateurs et les proxies ont leurs propres timeouts. Les limites de taille sont évidentes ; les timeouts sont sournois. Une liaison montante lente peut transformer un upload de 100 Mo en fête des timeouts.

Playbook de diagnostic rapide

Première étape : identifier qui refuse la requête

  • Si le client reçoit 413 instantanément, le rejet vient presque certainement de Nginx/Apache/proxy/WAF. PHP n’a même pas été invité.
  • Si vous obtenez un avertissement PHP à propos de upload_max_filesize, PHP a vu les en-têtes de la requête et a décidé que le corps est trop volumineux.
  • Si vous obtenez une erreur générique d’appli après attente, suspectez des timeouts, un disque temporaire plein ou une validation applicative.

Deuxième étape : confirmer quel SAPI PHP et quelle config vous modifiez

  • La config PHP CLI est sans rapport pour les uploads web sauf si vous exécutez des uploads via CLI (ce qui est rare).
  • Pour le web : vous êtes sur PHP-FPM (commun) ou Apache mod_php (moins courant sur Ubuntu 24.04).
  • Confirmez le chemin ini actif, puis confirmez les overrides de pool.

Troisième étape : trouvez la plus petite limite dans la chaîne

  • Limites Edge/WAF/CDN (caps de corps de requête) et règles d’ingress.
  • Nginx : client_max_body_size ; buffering du proxy ; timeouts.
  • Apache : limites de corps et règles des modules de sécurité.
  • PHP : upload_max_filesize, post_max_size, memory_limit, timeouts, répertoire tmp.
  • Appli : règles de validation et valeurs par défaut du framework.
  • Disque : espace libre + inodes + permissions.

Tâches pratiques (commandes, sorties, décisions)

Voici ce que j’exécute réellement sur Ubuntu 24.04 quand les uploads échouent. Chaque tâche inclut : commande, ce que signifie la sortie, et la décision que vous prenez.

Task 1: Verify the web stack (Nginx vs Apache) on the host

cr0x@server:~$ systemctl status nginx apache2 --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-30 09:12:31 UTC; 2h 7min ago

● apache2.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/apache2.service; disabled; preset: enabled)
     Active: inactive (dead)

Signification : Nginx est en jeu ; Apache ne l’est pas. Si vous modifiez la config Apache, vous décorez le mauvais bâtiment.

Décision : Concentrez-vous sur les limites Nginx et PHP-FPM. Ignorez Apache.

Task 2: Confirm PHP-FPM is used and which version

cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-30 09:10:07 UTC; 2h 10min ago

Signification : PHP 8.3 FPM sert le PHP web. Ubuntu 24.04 fournit PHP 8.3 par défaut, donc cela concorde.

Décision : Tous les réglages d’upload PHP doivent être appliqués à la config FPM, pas à la CLI.

Task 3: Find the active php.ini for PHP-FPM (not CLI)

cr0x@server:~$ php-fpm8.3 -i | grep -E 'Loaded Configuration File|Scan this dir for additional .ini files|upload_max_filesize|post_max_size' | head
Loaded Configuration File => /etc/php/8.3/fpm/php.ini
Scan this dir for additional .ini files => /etc/php/8.3/fpm/conf.d
upload_max_filesize => 2M => 2M
post_max_size => 8M => 8M

Signification : Le SAPI FPM charge /etc/php/8.3/fpm/php.ini. Les valeurs par défaut sont petites (2M/8M), ce qui explique pourquoi votre PDF pleure.

Décision : Éditez le ini FPM (ou un override dans conf.d) et augmentez aussi post_max_size en parallèle de upload_max_filesize.

Task 4: Confirm the CLI values are irrelevant (but useful for catching confusion)

cr0x@server:~$ php -i | grep -E 'Loaded Configuration File|upload_max_filesize|post_max_size' | head
Loaded Configuration File => /etc/php/8.3/cli/php.ini
upload_max_filesize => 128M => 128M
post_max_size => 128M => 128M

Signification : La CLI a déjà 128M, mais FPM est encore à 2M/8M. C’est la situation la plus fréquente « Je jure que je l’ai changé ».

Décision : Arrêtez d’éditer l’ini CLI pour des problèmes web. Corrigez FPM.

Task 5: Check for pool-level overrides that silently win

cr0x@server:~$ grep -R --line-number -E 'php_(admin_)?value\[(upload_max_filesize|post_max_size)\]' /etc/php/8.3/fpm/pool.d
/etc/php/8.3/fpm/pool.d/www.conf:402:php_admin_value[upload_max_filesize] = 10M
/etc/php/8.3/fpm/pool.d/www.conf:403:php_admin_value[post_max_size] = 10M

Signification : Même si vous réglez 128M dans php.ini, le pool force 10M et vous perdez. php_admin_value n’est pas négociable au runtime.

Décision : Mettez à jour le fichier de pool ou supprimez l’override. Puis redémarrez FPM.

Task 6: Apply sane PHP settings (edit) and validate the syntax

cr0x@server:~$ sudo sed -i 's/^upload_max_filesize = .*/upload_max_filesize = 128M/; s/^post_max_size = .*/post_max_size = 128M/' /etc/php/8.3/fpm/php.ini
cr0x@server:~$ sudo php-fpm8.3 -tt
[30-Dec-2025 11:22:14] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful

Signification : La config est analysée. Si elle ne l’est pas, ne redémarrez pas ; corrigez d’abord.

Décision : Procédez au redémarrage de PHP-FPM.

Task 7: Restart PHP-FPM (don’t “reload and hope”)

cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ systemctl is-active php8.3-fpm
active

Signification : PHP-FPM est de nouveau en ligne. Le redémarrage est brutal, mais déterministe.

Décision : Vérifiez maintenant les valeurs effectives du service en cours d’exécution.

Task 8: Confirm effective values via a local FastCGI request (no browser guesswork)

cr0x@server:~$ printf '%s\n' '<?php echo ini_get("upload_max_filesize"),"\n",ini_get("post_max_size"),"\n";' | sudo tee /var/www/html/_ini.php > /dev/null
cr0x@server:~$ curl -sS http://127.0.0.1/_ini.php
128M
128M

Signification : Le PHP servi par le web rapporte maintenant 128M/128M. C’est la vérité qui compte.

Décision : Si les uploads échouent encore, le goulot est en amont (Nginx/proxy) ou en aval (tmp disk, appli).

Task 9: Check Nginx request body limit

cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number 'client_max_body_size' -n /dev/stdin | head
# (no output)

Signification : Aucun réglage explicite trouvé. Nginx utilisera son défaut, qui est typiquement 1m.

Décision : Définissez client_max_body_size dans le bon contexte (server ou location) et rechargez Nginx.

Task 10: Add client_max_body_size and validate Nginx config

cr0x@server:~$ sudo tee /etc/nginx/conf.d/uploads.conf > /dev/null <<'EOF'
server {
    listen 80 default_server;
    server_name _;
    client_max_body_size 128m;
}
EOF
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
cr0x@server:~$ systemctl is-active nginx
active

Signification : Nginx acceptera des corps jusqu’à 128 Mo pour ce serveur. La config est chargée.

Décision : Retestez les uploads. Si votre vhost de production n’est pas le serveur par défaut, placez la directive dans le vrai vhost, pas dans cet exemple.

Task 11: Catch a proxy/CDN limit by reproducing and watching status codes

cr0x@server:~$ curl -sS -o /dev/null -w '%{http_code}\n' -F 'file=@/var/log/syslog' http://127.0.0.1/upload.php
200

Signification : Localement c’est OK. Si la même requête via le nom d’hôte public renvoie 413, votre edge/proxy est le limiteur.

Décision : Comparez le chemin local et public. Si local fonctionne et public échoue, arrêtez de toucher PHP et corrigez le proxy/WAF/ingress.

Task 12: Inspect logs for the layer that’s complaining

cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/30 11:40:01 [error] 24177#24177: *812 client intended to send too large body: 187654321 bytes, client: 203.0.113.44, server: example, request: "POST /upload.php HTTP/1.1", host: "example"

Signification : Nginx a refusé le corps. Cette ligne d’erreur est pratiquement une confession.

Décision : Corrigez le scope de la config Nginx (vhost/location erroné), rechargez et réessayez.

Task 13: Check PHP-FPM logs for “file too large” vs “no temp dir” vs timeouts

cr0x@server:~$ sudo tail -n 50 /var/log/php8.3-fpm.log
[30-Dec-2025 11:42:18] WARNING: [pool www] child 30112 said into stderr: "PHP Warning:  POST Content-Length of 187654321 bytes exceeds the limit of 134217728 bytes in Unknown on line 0"

Signification : C’est PHP qui impose post_max_size. Il compte tout le corps. Votre « 128M » peut être trop juste une fois l’overhead multipart ajouté.

Décision : Réglez post_max_size plus haut que upload_max_filesize (pratique courante : +10–20%).

Task 14: Check temp storage space and inode health

cr0x@server:~$ df -h /tmp /var/tmp /var/lib/php/sessions
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           3.1G  3.0G   40M  99% /tmp
/dev/sda2        80G   52G   25G  68% /

Signification : /tmp est en tmpfs et presque plein. PHP peut écrire des fichiers temporaires là (selon upload_tmp_dir), et « 99% » flirte avec l’échec.

Décision : Libérez de l’espace, ou déplacez upload_tmp_dir vers un système de fichiers réel avec de la marge.

Task 15: Verify what PHP thinks the temp directory is

cr0x@server:~$ curl -sS http://127.0.0.1/_ini.php | cat
128M
128M
cr0x@server:~$ php-fpm8.3 -i | grep -E '^upload_tmp_dir|^sys_temp_dir' | head
upload_tmp_dir => no value => no value
sys_temp_dir => no value => no value

Signification : Sans valeur explicite, PHP utilise le répertoire tmp système (souvent /tmp). Si /tmp est un tmpfs contraint, vous obtenez des échecs aux allures aléatoires.

Décision : Définissez upload_tmp_dir vers un répertoire sur disque avec les permissions correctes.

Task 16: Set upload_tmp_dir and verify permissions

cr0x@server:~$ sudo install -d -o www-data -g www-data -m 1733 /var/tmp/php-uploads
cr0x@server:~$ sudo grep -n '^upload_tmp_dir' /etc/php/8.3/fpm/php.ini || true
cr0x@server:~$ echo 'upload_tmp_dir = /var/tmp/php-uploads' | sudo tee -a /etc/php/8.3/fpm/php.ini > /dev/null
cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/tmp/php-uploads/.permtest && ls -la /var/tmp/php-uploads/.permtest'
-rw-r--r-- 1 www-data www-data 0 Dec 30 11:55 /var/tmp/php-uploads/.permtest

Signification : Le répertoire existe, a des permissions de type sticky bit (1733) et l’utilisateur FPM peut écrire dedans.

Décision : Cela élimine le goulot tmpfs. Si les échecs persistent, examinez les timeouts ou la validation applicative.

D’où viennent réellement les valeurs PHP (priorités qui mordent)

Quand quelqu’un dit « Je l’ai mis dans php.ini », ma première question est : « Quel php.ini, pour quel SAPI, et quelque chose l’a-t‑il écrasé ? » La configuration PHP est en couches, et Ubuntu facilite le fait de changer la mauvaise couche.

L’ordre de puissance (du « qui gagne le plus » au « qui gagne le moins »)

  1. Config du pool PHP-FPM via php_admin_value[] et php_value[] dans /etc/php/8.3/fpm/pool.d/*.conf.
  2. Config par répertoire (seulement pour certains SAPIs) : .user.ini pour PHP-FPM, .htaccess pour Apache mod_php. (Oui, vous pouvez avoir les deux pendant une migration confuse.)
  3. Fichiers ini additionnels dans /etc/php/8.3/fpm/conf.d/ chargés dans l’ordre numérique.
  4. php.ini principal pour ce SAPI : /etc/php/8.3/fpm/php.ini.
  5. Valeurs compilées par défaut (les valeurs que vous obtenez si vous ne configurez rien, ce que les environnements de staging font souvent par accident).

Pourquoi php_admin_value est un piège

php_admin_value est intentionnellement « admin only ». Il ne peut pas être écrasé par le code applicatif, et il écrasera vos ini. C’est fantastique pour l’hébergement multi‑tenant et pour empêcher une appli de monopoliser la mémoire. C’est aussi la façon dont vous vous retrouvez avec un pool bloqué à 10M pendant que tout le monde se dispute pour savoir pourquoi les changements de php.ini « ne fonctionnent pas ».

Que faire sur Ubuntu 24.04

Choisissez une stratégie et soyez cohérent :

  • Serveur mono‑appli : placez les réglages globaux d’upload dans /etc/php/8.3/fpm/php.ini ou dans un fichier dédié /etc/php/8.3/fpm/conf.d/99-uploads.ini. Évitez les overrides de pool sauf si nécessaire.
  • Hôte multi‑applis : gardez php.ini conservateur et mettez des limites par pool dans chaque fichier pool.d. Documentez-les. Sérieusement. Le futur‑vous oubliera.

Une citation à garder en tête quand vous touchez aux réglages de production : « L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan. Elle s’applique douloureusement bien à « j’ai rechargé et j’ai espéré que ça prenne. »

Nginx/Apache/proxies : les autres limites

Les uploads meurent en amont plus souvent qu’ils ne meurent dans PHP. Ce n’est pas parce que PHP est infaillible ; c’est parce que refuser un corps surdimensionné tôt coûte moins cher que le bufferiser puis dire à PHP de gérer.

Nginx : client_max_body_size et où le définir

client_max_body_size peut être défini dans les contextes http, server ou location. « Je l’ai mis dans nginx.conf » n’est pas une information suffisante. Si vous l’avez mis dans un fichier qui n’est pas inclus, ou dans un bloc server qui n’est pas utilisé, rien ne change.

De plus, si vous utilisez plusieurs blocs server (redirection HTTP vers HTTPS, vhosts internes/externes séparés), vous devez le placer là où le point d’upload est réellement servi.

Apache : les limites de taille sont une hydre

Si vous êtes sur Apache, vous pourriez avoir affaire à :

  • limites de requête du core (varie selon la version/les modules utilisés)
  • règles mod_security qui plafonnent la taille du corps ou rejettent les patterns multipart
  • modules proxy quand Apache sert de front à PHP-FPM

En pratique, dans les environnements Apache, on trouve souvent que la limite se situe dans le middleware de sécurité plutôt que dans Apache lui‑même.

Reverse proxies et CDNs : le « non » silencieux

Même quand votre origine est correcte, un edge peut refuser un gros corps :

  • Les politiques WAF managées peuvent plafonner les corps de requête.
  • Les ingress controllers dans les plateformes conteneurisées ont souvent des caps par défaut.
  • Les load balancers peuvent imposer des limites sur la taille des headers et du corps.

Le symptôme est généralement : curl local vers localhost fonctionne ; upload externe échoue avec 413 ou une page d’erreur brandée par le fournisseur. Vos logs d’origine sont propres parce que la requête n’est jamais arrivée.

Spécificités PHP-FPM sur Ubuntu 24.04

Les quatre réglages PHP qui comptent pour les uploads

  • upload_max_filesize : taille maximale d’un fichier uploadé individuel.
  • post_max_size : taille maximale de l’ensemble du corps POST. Doit être >= taille de l’upload plus l’overhead.
  • memory_limit : ne limite pas directement la taille d’un upload, mais beaucoup d’applis lisent les uploads en mémoire ou les traitent (redimensionnement d’images) et alors cela devient le plafond réel.
  • max_input_time / max_execution_time : les gros uploads sur des liaisons lentes peuvent atteindre ces limites. Surveillez aussi les timeouts Nginx/FPM.

Quelle taille leur donner ?

Soyez délibéré. « Tout mettre à 2G » n’est pas de l’ingénierie ; c’est une reddition.

  • Réglez upload_max_filesize sur votre besoin métier (exemple : 128M).
  • Réglez post_max_size un peu plus haut (exemple : 140M–160M) car l’encodage multipart ajoute de l’overhead et les formulaires peuvent contenir des champs.
  • Réglez les caps du serveur web/proxy un peu au‑dessus de post_max_size.
  • Assurez‑vous que le stockage temporaire peut absorber au moins quelques uploads concurrents à cette taille.

Alignement des timeouts (la réalité des uploads lents)

Si vos utilisateurs uplodent depuis un Wi‑Fi d’hôtel, l’upload peut prendre des minutes. C’est normal. Votre stack doit accepter d’attendre aussi longtemps.

  • Nginx : client_body_timeout, proxy_read_timeout (si proxy), et les réglages de buffering.
  • PHP-FPM : timeouts de terminaison des requêtes (les réglages de pool peuvent tuer les « slow requests »).
  • Appli : traitement en background vs traitement synchrone.

Blague n°2 : Les limites d’upload sont le seul endroit où « encore un méga » peut provoquer un incident de production. C’est un régime pour serveurs, et eux aussi trichent.

Pièges au niveau applicatif (frameworks et CMS)

Une fois que le corps de la requête survit à la guérite, votre application peut encore rejeter le fichier. C’est là que les équipes perdent du temps parce que le message d’erreur ressemble à un « problème serveur » alors que c’est de la logique métier.

Règles de validation qui écrasent l’infrastructure

Exemples que vous verrez :

  • Validation Laravel : règles max: en kilo‑octets.
  • Contraintes d’upload Symfony réglées sur une valeur conservatrice dans un type de formulaire.
  • WordPress : « Taille maximale de fichier uploadée » de l’UI dépend des valeurs PHP, mais des plugins peuvent imposer des règles plus strictes.
  • Apps custom : un fichier de config plafonne la taille « pour sécurité » et personne ne l’a mis à jour.

Explosions de mémoire lors du post‑processing

Votre limite d’upload peut être 128M, mais votre appli peut ensuite :

  • lire le fichier entier en mémoire (file_get_contents sur un fichier de 128M est radical)
  • transcoder ou redimensionner des images (pics de mémoire temporaires)
  • hasher le fichier en userland PHP

C’est là que la vraie erreur est 500, « Allowed memory size exhausted », ou un worker qui meurt. La solution n’est pas « augmenter encore upload_max_filesize ». La solution est de streamer, chunker, ou déplacer le traitement hors du chemin de la requête.

Trois mini‑histoires d’entreprise issues des uploads

1) Incident causé par une mauvaise hypothèse : « Nous avons changé php.ini, donc c’est réglé »

Une plateforme interne de taille moyenne devait accepter des pièces jointes de factures plus grosses. L’équipe a mis à jour /etc/php/8.3/cli/php.ini sur quelques serveurs, a testé avec un script CLI qui parse des fichiers, et a considéré le travail terminé. Le lendemain, l’interface web refusait toujours tout fichier au‑delà de 10 Mo.

Les opérations ont été alertées car le taux d’erreurs a grimpé. Les logs applicatifs ne contenaient rien d’utile. Le canal d’incident s’est rempli du bruit habituel : « Peut‑être la base », « Peut‑être le stockage », « Peut‑être le load balancer ». Personne ne voulait dire l’évidence : le changement pourrait ne pas s’être appliqué au runtime qui compte.

Le problème réel était double. D’abord, PHP-FPM chargeait /etc/php/8.3/fpm/php.ini, qui n’avait pas été modifié. Ensuite, le pool FPM avait php_admin_value[post_max_size] défini à 10M après une ancienne série de durcissements “protéger l’hôte”.

La correction a pris quelques minutes une fois que quelqu’un a arrêté de deviner : confirmer le SAPI, grep les overrides de pool, augmenter les limites, redémarrer FPM, puis vérifier avec un endpoint servi par le web. La leçon n’était pas « faites attention ». C’était « validez les changements à la couche que les utilisateurs frappent, pas à la couche que vous préférez tester. »

2) Optimisation qui a mal tourné : « On va moins bufferiser pour améliorer les perfs »

Une équipe utilisant Nginx devant PHP-FPM a tenté de réduire les I/O disque en changeant le comportement de buffering des requêtes. Leur raisonnement était sensé : les gros uploads écrivaient sur le disque puis PHP les relisait. Double I/O, plaisir divisé.

Ils ont modifié les paramètres de buffering et quelques timeouts pour « accélérer ». En test sur LAN rapide, tout semblait parfait. En production, les utilisateurs sur connexions lentes voyaient des échecs intermittents : les uploads mourraient autour de 30–60 secondes. Pendant ce temps, le backend voyait des pics de connexions à demi‑ouvertes et des workers FPM bloqués.

L’optimisation a mal tourné parce qu’elle a déplacé la pression en amont. Plutôt que de bufferiser les corps en sécurité (avec une utilisation prévisible des ressources), l’origine gardait les connexions ouvertes plus longtemps. Les timeouts sont devenus le facteur limitant, et le modèle de concurrence s’est dégradé : moins de requêtes complétées, plus de tâches en vol, et une boucle où les retries empirent la situation.

La correction a été ennuyeuse : réactiver un buffering sensé pour les endpoints d’upload, définir des caps explicites, aligner les timeouts sur les conditions réelles des utilisateurs, et instrumenter les compteurs « request aborted ». Ils ont ensuite amélioré les perfs correctement, mais en changeant l’architecture (uploads directs vers un stockage objet) plutôt qu’en essayant de tromper la physique au niveau du proxy.

3) Pratique ennuyeuse mais correcte qui a sauvé la mise : « Prouvez-le localement et vérifiez couche par couche »

Une application d’entreprise réglementée a commencé à échouer des uploads après une réinstallation routinière d’un hôte Ubuntu 24.04. Même playbook, même Ansible, mêmes configs — soi‑disant. Le helpdesk rapportait « les uploads > 5 Mo échouent », ce qui n’est pas un diagnostic, c’est un signal de détresse.

L’ingénieur d’astreinte n’a touché à aucune config au début. Il a reproduit le problème avec un curl local vers 127.0.0.1 et a vu que ça marchait. Puis il a répété la même requête via le nom d’hôte public et obtenu un 413. Cette différence a séparé l’univers en deux : l’origine fonctionne, l’edge rejette.

Ensuite, ils ont vérifié les réglages de l’ingress controller et trouvé un cap de taille de requête par défaut qui avait changé avec une mise à jour du chart. Personne ne l’avait épinglé, parce que « les défauts vont bien » jusqu’à ce qu’ils ne le soient plus. L’ingénieur a augmenté le cap pour qu’il corresponde aux limites Nginx et PHP de l’origine, déployé, et vérifié avec le même curl.

Pas d’héroïsme. Pas de refactor à minuit. Juste une approche disciplinée : reproduire localement, comparer les chemins, identifier la première couche qui rejette. L’ennuyeux, c’est bien. L’ennuyeux scale.

Erreurs courantes (symptômes → cause racine → correction)

1) Symptom: 413 “Request Entity Too Large” immediately

Cause racine : Nginx/Apache/proxy/WAF a rejeté le corps avant PHP.

Correction : Définissez les caps de corps de requête à la couche qui rejette (par ex. Nginx client_max_body_size). Vérifiez avec un curl local vs public, et consultez les logs du serveur web pour « intended to send too large body. »

2) Symptom: “exceeds upload_max_filesize directive” in the browser/app

Cause racine : La limite PHP est inférieure à la taille du fichier ; souvent vous avez modifié l’ini CLI, pas FPM.

Correction : Confirmez le chemin ini FPM avec php-fpm8.3 -i, mettez à jour upload_max_filesize et post_max_size, redémarrez FPM, validez via un endpoint web avec ini_get.

3) Symptom: Upload starts, then fails after some time with 504/timeout

Cause racine : Timeouts dans le proxy/serveur web/FPM ; les clients lents dépassent client_body_timeout ou les timeouts upstream.

Correction : Alignez les timeouts entre les couches pour les durées d’upload attendues ; envisagez des endpoints d’upload dédiés avec des timeouts plus longs, ou des uploads directs vers du stockage objet.

4) Symptom: Random failures under load; small files work, big ones sometimes work

Cause racine : Pression sur le système temporaire (tmpfs plein), exhaustion d’inodes, ou contention I/O disque. PHP écrit d’abord des fichiers temporaires.

Correction : Surveillez l’utilisation de /tmp, définissez upload_tmp_dir vers un FS spacieux, et gardez la concurrence en tête (N uploads concurrents * taille).

5) Symptom: PHP logs show POST Content-Length exceeds limit even though upload_max_filesize is high

Cause racine : post_max_size est inférieur à la taille totale de la requête multipart.

Correction : Réglez post_max_size plus haut que upload_max_filesize ; laissez une marge pour l’overhead multipart et les autres champs.

6) Symptom: 500 errors when uploading large images/videos

Cause racine : Le post‑processing applicatif atteint memory_limit ou le temps d’exécution. L’upload a réussi ; c’est le traitement qui échoue.

Correction : Traitez en streaming, mettez en queue les tâches lourdes, augmentez la mémoire quand c’est justifié, et plafonnez les tailles dans l’appli pour correspondre à la capacité de traitement.

7) Symptom: “It works on one server but not another”

Cause racine : Overrides par pool, scope de vhost différent, includes manquants, ou chemin proxy différent.

Correction : Comparez les outputs de config effectifs : nginx -T, grep des pools FPM pour php_admin_value, et un dump ini servi par le web.

Checklists / plan étape par étape

Étape par étape : augmenter les limites d’upload en toute sécurité sur Ubuntu 24.04 (Nginx + PHP-FPM)

  1. Choisissez une cible : ex. fichier max 128M. Décidez en fonction des besoins produit, pas des impressions.
  2. Définissez les limites PHP :
    • upload_max_filesize = 128M
    • post_max_size = 160M (marge)
  3. Vérifiez les overrides de pool : supprimez ou augmentez php_admin_value dans la config de pool.
  4. Définissez la limite Nginx : client_max_body_size 160m; dans le server/location correct.
  5. Vérifiez le stockage temporaire : assurez‑vous que le répertoire tmp peut gérer les uploads concurrents au pic.
  6. Redémarrez les services : redémarrez PHP-FPM ; rechargez Nginx après nginx -t.
  7. Vérifiez depuis le chemin web : curl l’endpoint de l’appli ou un petit script diagnostic.
  8. Vérifiez depuis le chemin public : si vous avez un edge/proxy, testez aussi à travers lui.
  9. Instrumentez et loggez : surveillez les 413, les timeouts upstream, et l’utilisation disque.
  10. Documentez les limites choisies : notez où elles sont définies (ini, pool, vhost, proxy). Le futur‑vous n’est pas médium.

Checklist : quand vous NE DEVEZ PAS augmenter les limites

  • Vous ne savez pas qui a besoin d’uploads plus gros et pourquoi.
  • Vous n’avez pas d’espace disque pour les fichiers temporaires.
  • Votre application lit tout le fichier en mémoire et vous ne voulez pas corriger ça maintenant.
  • Vous êtes derrière un edge avec un cap strict que vous ne pouvez pas changer (vous aurez juste un comportement incohérent).

Checklist : garde‑fous opérationnels

  • Définissez des bornes supérieures par endpoint. Tous les POST ne doivent pas accepter 160 MB.
  • Utilisez des blocs server/location séparés pour les endpoints d’upload avec des limites/timeouts adaptés.
  • Alertez sur le taux de 413 et l’utilisation du filesystem temporaire.
  • Suivez les durées d’upload (p95/p99). Les uploads lents sont le terrain des bugs de timeout.

FAQ

1) J’ai changé upload_max_filesize mais phpinfo() affiche toujours l’ancienne valeur. Pourquoi ?

Vous avez probablement modifié l’ini CLI ou le mauvais ini FPM, ou un override de pool gagne. Vérifiez avec php-fpm8.3 -i et grep dans pool.d pour php_admin_value.

2) Dois‑je définir à la fois upload_max_filesize et post_max_size ?

Oui. Les uploads multipart sont des corps POST. Si post_max_size est plus petit, PHP rejette la requête avant que la limite de fichier ne soit pertinente.

3) Quelle valeur pour post_max_size par rapport à upload_max_filesize ?

Plus grande. Donnez‑lui une marge (10–25 % est courant) car les délimitations multipart et les champs additionnels augmentent la taille totale du corps.

4) Pourquoi j’ai un 413 alors que PHP est configuré correctement ?

Parce que 413 est généralement émis par Nginx/Apache/proxy/WAF. PHP ne voit pas la requête. Consultez les logs de la couche qui rejette et sa directive de taille de corps de requête.

5) memory_limit doit‑il être supérieur à upload_max_filesize ?

Pas strictement pour l’upload lui‑même, car PHP stocke les uploads en fichiers temporaires. Mais votre application peut lire/traiter le fichier en mémoire. Si c’est le cas, la mémoire devient la vraie limite.

6) Pourquoi les uploads échouent uniquement pour certains utilisateurs ?

Les connexions lentes déclenchent des timeouts ; des proxies d’entreprise peuvent avoir leurs propres caps ; les réseaux mobiles rendent le comportement instable. Alignez les timeouts et testez en simulant des réseaux bridés.

7) Puis‑je définir des limites d’upload par site sur le même serveur ?

Oui. Utilisez des pools PHP-FPM séparés avec php_admin_value par pool, ou des blocs server Nginx séparés avec différents client_max_body_size. Soyez explicite et documentez.

8) Dois‑je compter sur .user.ini pour définir les limites d’upload ?

Seulement si vous avez besoin d’un contrôle par répertoire et que vous acceptez une ambiguïté opérationnelle. La configuration centrale (pool FPM ou ini) est plus facile à auditer et moins surprenante lors des incidents.

9) Mes uploads échouent avec « No such file or directory » dans PHP. Pourquoi ?

C’est souvent le répertoire tmp : absent, permissions incorrectes, ou plein. Définissez upload_tmp_dir vers un dossier writable par l’utilisateur FPM et assurez‑vous qu’il y a de l’espace.

10) Devrait‑on accepter de très gros uploads via PHP ?

Parfois oui (outils internes, faible volume). Pour des fichiers très volumineux ou à fort trafic, envisagez des uploads directs vers un stockage objet avec URLs signées, puis traitez de façon asynchrone.

Prochaines étapes réalisables aujourd’hui

  1. Mesurez l’échec : reproduisez avec curl localement et via le nom d’hôte public. Identifiez si l’origine reçoit la requête.
  2. Verrouillez la config effective : confirmez les valeurs FPM via un endpoint web, et vérifiez la config Nginx via nginx -T.
  3. Définissez des limites alignées : cap proxy/Nginx ≥ PHP post_max_size > PHP upload_max_filesize.
  4. Corrigez le stockage temporaire : arrêtez d’écrire de gros fichiers temporaires sur un tmpfs presque plein à moins d’aimer les échecs intermittents.
  5. Décidez de l’architecture : si les uploads sont cruciaux et volumineux, planifiez un modèle direct‑vers‑stockage et sortez PHP du chemin critique.

Si vous ne faites qu’une chose : arrêtez de deviner quel ini est actif, et prouvez‑le depuis le runtime servi par le web. Les bugs d’upload prospèrent dans l’ambiguïté. Tuez l’ambiguïté.

← Précédent
Priorité des variables d’environnement Docker : pourquoi votre configuration n’est jamais celle que vous croyez
Suivant →
286 expliqué : le mode protégé qui a sauvé les PC — et torturé les développeurs

Laisser un commentaire