Хтось намагається завантажити PDF на 40 МБ. Інтерфейс крутиться, потім «помирає» з гордим «413» або з розмитим «The uploaded file exceeds the upload_max_filesize directive.» Ви піднімаєте upload_max_filesize до 128M, перезавантажуєте щось, і… нічого не змінюється. Класика.
На Ubuntu 24.04 проблема рідко в тому, що «PHP має ліміт». Проблема в тому, що «ви змінили неправильний ліміт, у неправильному місці, для неправильної SAPI, за проксі, який не отримав повідомлення». Виправимо там, де це справді має значення.
Ментальна модель: завантаження — це естафета
Завантаження файлу — це не «фішка PHP». Це HTTP-запит з тілом. Це тіло проходить через ланцюжок:
- Клієнт (браузер/мобільний додаток) формує multipart/form-data запит.
- Будь‑який edge/WAF/CDN/реверс‑проксі накладає свої ліміти на розмір запиту та таймаути.
- Ваш load balancer або ingress робить те ж саме.
- Веб‑сервер (Nginx/Apache) вирішує, буферизувати, стрімити чи відхилити тіло.
- PHP‑FPM отримує запит, записує тимчасові файли завантаження, потім додаток читає/переміщує їх.
- Ваш додаток/фреймворк може мати власні валідаційні ліміти і відхилити файл.
- Сховище (квота, права доступу, заповнений диск) може зіпсувати стадію тимчасових файлів.
Потрібно змінити перший у ланцюгу ліміт, який менший за вашу ціль. Змінювати пізніший — це як розширювати з’їзд з траси, коли перед мостом усе ще одна смуга.
Жарт №1: Піднімати upload_max_filesize, не перевіривши Nginx, — як купувати більшу валізу для рейсу тільки з ручною поклажею. Вас усе одно зустріне агент у гейті.
Цікаві факти та історичний контекст
- Обмеження завантажень у PHP були розроблені з огляду на shared hosting. Ранні розгортання PHP припускали багато невзаємопов’язаних користувачів на одній машині; консервативні значення за замовчуванням допомагали запобігти «одне завантаження вбиває сервер».
post_max_sizeіснує тому, що завантаження є частиною тіла POST. Multipart‑завантаження — це все ще просто POST‑дані; PHP рахує все тіло запиту, а не лише файл.- Nginx історично мав дефолт 1 MB для тіла запиту. Цей дефолт зіпсував більше п’ятничних релізів, ніж більшість людей визнає.
- Apache має кілька регуляторів залежно від модулів. Історія ліміту тіла запиту змінюється в залежності від базових налаштувань, mod_security та proxy‑модулів.
- 413 — це не «помилка PHP». Це HTTP‑статус, що означає, що сервер (або проксі) відмовився від тiла запиту; PHP може й не бачити його.
- Завантаження PHP потрапляють на диск до того, як ваш код їх побачить. Файл потрапляє в
upload_tmp_dir(або системний temp); якщо цей ФС заповнений, ви отримаєте загадкові збої. - PHP‑FPM додав перевизначення на рівні пулів для мульти‑тенантності. Це зручно, поки ви не забудете, що пул має
php_admin_value, який мовчки переважає ini‑файли. - systemd змінив звички «перезавантаження» багатьох адміністраторів. На сучасній Ubuntu «restart the daemon» надійніший; «reload the config» залежить від сервісу і вашого терпіння.
- Браузери і проксі мають власні таймаути. Ліміти за розміром очевидні; таймаути — підступні. Повільний uplink може перетворити 100 MB завантаження на таймаут‑вечірку.
Швидкий план діагностики
Перше: визначте, хто відхиляє запит
- Якщо клієнт отримує 413 миттєво, відхилення майже напевно відбувається в Nginx/Apache/proxy/WAF. PHP навіть не був запрошений.
- Якщо ви бачите PHP‑попередження про
upload_max_filesize, PHP побачив заголовки запиту і вирішив, що тіло занадто велике. - Якщо ви отримуєте загальну помилку додатку після очікування — підозрюйте таймаути, заповнений тимчасовий диск або валідацію додатку.
Друге: підтвердьте, яку PHP SAPI і конфіг ви змінюєте
- CLI‑конфіг PHP не має значення для веб‑завантажень, хіба що ви запускаєте завантаження через CLI (що малоймовірно).
- Для вебу: ви або на PHP‑FPM (поширено), або на Apache mod_php (менш поширено на Ubuntu 24.04).
- Підтвердіть активний шлях до ini, потім перевірте перевизначення пулу.
Третє: знайдіть найменший ліміт у ланцюгу
- Edge/WAF/CDN ліміти (caps тіла запиту) та правила ingress.
- Nginx:
client_max_body_size; буферизація проксі; таймаути. - Apache: обмеження тіла запиту та правила модулів безпеки.
- PHP:
upload_max_filesize,post_max_size,memory_limit, таймаути, тимчасова директорія. - Додаток: валідаційні правила та дефолти фреймворку.
- Диск: вільне місце + наявність inode + права доступу.
Практичні завдання (команди, виводи, рішення)
Ось те, що я реально виконую на Ubuntu 24.04, коли завантаження не працюють. Кожне завдання містить: команду, що означає вивід, і рішення, яке ви приймаєте.
Завдання 1: Перевірте стек вебу (Nginx чи Apache) на хості
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)
Значення: У грі — Nginx; Apache не активний. Якщо ви правите конфіг Apache, ви прикрашаєте не ту будівлю.
Рішення: Зосередьтеся на обмеженнях Nginx та PHP‑FPM. Ігноруйте Apache.
Завдання 2: Підтвердіть, що використовується PHP-FPM і яка версія
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
Значення: PHP 8.3 FPM обслуговує веб‑PHP. Ubuntu 24.04 постачає PHP 8.3 за замовчуванням, отже все логічно.
Рішення: Усі налаштування завантажень PHP потрібно застосувати до конфіга FPM, а не CLI.
Завдання 3: Знайдіть активний php.ini для PHP-FPM (не 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
Значення: FPM SAPI завантажує /etc/php/8.3/fpm/php.ini. Значення за замовчуванням малі (2M/8M), отже ваш PDF плаче.
Рішення: Редагуйте FPM ini (або додайте override в conf.d) і підніміть post_max_size разом із upload_max_filesize.
Завдання 4: Підтвердіть, що CLI‑значення неважливі (але корисні для плутанини)
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
Значення: CLI вже має 128M, але FPM ще на 2M/8M. Це найпоширеніша ситуація «я ж змінив».
Рішення: Припиніть редагувати CLI ini для веб‑проблем. Виправте FPM.
Завдання 5: Перевірте перевизначення на рівні пулу, які мовчки перемагають
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
Значення: Навіть якщо ви поставите 128M в php.ini, пул примусово ставить 10M і ви програєте. php_admin_value не підлягає змінам під час виконання.
Рішення: Оновіть файл пулу або видаліть перевизначення. Потім перезапустіть FPM.
Завдання 6: Застосуйте розумні налаштування PHP (редагування) і перевірте синтаксис
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
Значення: Конфіг парситься. Якщо ні — не перезапускайте; виправте спочатку.
Рішення: Можна продовжити до перезапуску PHP‑FPM.
Завдання 7: Перезапустіть PHP-FPM (не «reload and hope»)
cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ systemctl is-active php8.3-fpm
active
Значення: PHP‑FPM знову працює. Перезапуск рубає по‑грубому, але це детерміновано.
Рішення: Тепер перевірте ефективні значення з працюючого сервісу.
Завдання 8: Підтвердіть ефективні значення через локальний FastCGI‑запит (без здогадок браузера)
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
Значення: Веб‑PHP тепер звітує 128M/128M. Це правда, яка має значення.
Рішення: Якщо завантаження все ще падають, вузьке місце — вгору по ланцюгу (Nginx/proxy) або вниз (тимчасовий диск, додаток).
Завдання 9: Перевірте ліміт тіла запиту в Nginx
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number 'client_max_body_size' -n /dev/stdin | head
# (no output)
Значення: Явної налаштування не знайдено. Nginx використає свій дефолт, який зазвичай 1m.
Рішення: Встановіть client_max_body_size у правильному контексті (server або location) і перезавантажте Nginx.
Завдання 10: Додайте client_max_body_size і провалідуйте конфіг Nginx
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
Значення: Nginx прийматиме тіла до 128 MB для цього server. Конфіг завантажено.
Рішення: Повторно протестуйте завантаження. Якщо ваш production vhost — не default server, помістіть директиву у реальний vhost, а не в приклад.
Завдання 11: Виявити проксі/CDN‑ліміт, відтворивши запит і спостерігаючи коди статусу
cr0x@server:~$ curl -sS -o /dev/null -w '%{http_code}\n' -F 'file=@/var/log/syslog' http://127.0.0.1/upload.php
200
Значення: Локально працює. Якщо той самий запит через публічний хост повертає 413, ваш edge/proxy — обмежувач.
Рішення: Порівняйте локальний і публічний шлях. Якщо локально працює, а публічно — ні, припиніть чіпати PHP і виправте проксі/WAF/ingress.
Завдання 12: Перегляньте логи шару, який скаржиться
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"
Значення: Nginx відхилив тіло. Цей рядок у логах — майже зізнання.
Рішення: Виправте область дії конфіга Nginx (невірний vhost/location), перезавантажте і спробуйте знову.
Завдання 13: Перевірте логи PHP‑FPM на «file too large» проти «no temp dir» чи таймаутів
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"
Значення: Це PHP, який застосовує post_max_size. Він рахує все тіло. Ваші «128M» можуть бути занадто близько до межі, враховуючи multipart‑накладні витрати.
Рішення: Встановіть post_max_size вище за upload_max_filesize (звичайна практика: +10–20%).
Завдання 14: Перевірте вільне місце в тимчасовому сховищі та стан inode
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% /
Значення: /tmp — tmpfs і майже повний. PHP може писати тимчасові файли туди (залежно від upload_tmp_dir), і «99%» — це флірт з відмовою.
Рішення: Звільніть місце або перемістіть upload_tmp_dir на реальний ФС із запасом місця.
Завдання 15: Перевірте, що PHP вважає тимчасовою директорією
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
Значення: За відсутності явного значення PHP використовує системну тимчасову директорію (часто /tmp). Якщо /tmp — обмежений tmpfs, ви отримаєте випадкові збої при завантаженнях.
Рішення: Встановіть upload_tmp_dir у директорію на диску з правильними правами доступу.
Завдання 16: Встановіть upload_tmp_dir і перевірте права
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
Значення: Директорія існує, має права зі схожими на sticky біт (1733), і користувач FPM може туди записувати.
Рішення: Це прибирає вузьке місце tmpfs. Якщо помилки продовжуються, дивіться таймаути або валідацію додатку.
Звідки PHP-значення справді беруться (пріоритет, що підводить)
Коли хтось каже «я встановив у php.ini», моє перше питання: «Який php.ini, для якої SAPI, і чи щось його не перевизначило?» Конфігурація PHP шарувата, і Ubuntu робить це простим для того, щоб змінити невірний шар.
Порядок сили (від «має найбільшу силу» до «має найменшу»)
- Конфігурація пулу PHP-FPM через
php_admin_value[]іphp_value[]у/etc/php/8.3/fpm/pool.d/*.conf. - Пострічкова/каталогова конфігурація (лише в деяких SAPI):
.user.iniдля PHP‑FPM,.htaccessдля Apache mod_php. (Так, у міграційних випадках може бути і те, і інше.) - Додаткові ini‑файли в
/etc/php/8.3/fpm/conf.d/, що завантажуються в числовому порядку. - Головний php.ini для цієї SAPI:
/etc/php/8.3/fpm/php.ini. - Значення за замовчуванням з компіляції (те, що ви отримуєте, коли нічого не налаштовано, і що часто випадково робить staging).
Чому php_admin_value — це пастка
php_admin_value свідомо «тільки для адміністраторів». Його не можна перевизначити кодом додатку, і він переважає ваші ini‑налаштування. Це чудово для мульти‑тенантного хостингу і для запобігання перетворення одного додатку в пам’ятєвого монстра. Але саме так один пул застрягає на 10M, поки всі сперечаються, чому зміни в php.ini «не працюють».
Що робити на Ubuntu 24.04
Виберіть одну стратегію і дотримуйтеся її:
- Один сервер додатка: помістіть глобальні налаштування завантаження в
/etc/php/8.3/fpm/php.iniабо в окремий/etc/php/8.3/fpm/conf.d/99-uploads.ini. Уникайте перевизначень пулів, якщо немає вагомої причини. - Мульти‑додаток хост: тримайте php.ini консервативним і встановлюйте ліміти на рівні пулу в кожному файлі
pool.d. Документуйте їх. Серйозно. Майбутнє «ви» забуде.
Цитата, яку варто тримати в голові при зміні продакшн налаштувань: «Надія — це не стратегія.»
— General Gordon R. Sullivan. Це неприємно добре пасує до «Я перезавантажив і сподівався, що прийнялося».
Nginx/Apache/проксі: інші обмеження
Завантаження зазвичай помирають вгору по ланцюгу частіше, ніж у PHP. Це не тому, що PHP ідеальний; це тому, що відхилення надто великого тіла рано дешевше, ніж буферизувати його і потім змушувати PHP з ним розбиратися.
Nginx: client_max_body_size і де його ставити
client_max_body_size можна ставити в контексті http, server або location. «Я поставив його в nginx.conf» — це не достатньо інформації. Якщо ви поставили його в файл, який не включено, або в серверний блок, який не використовується, нічого не зміниться.
Також, якщо у вас кілька серверних блоків (редирект HTTP → HTTPS, окремі internal/external vhost), потрібно ставити його там, де реально обслуговується endpoint завантаження.
Apache: обмеження тіла запиту — гідра
Якщо ви на Apache, можливо, маєте справу з:
- основними обмеженнями запиту (залежить від версії/модулів)
- правилами mod_security, які капають розмір тіла запиту або відхиляють multipart‑шаблони
- proxy‑модулями, коли Apache фронтує PHP‑FPM
На практиці у Apache‑магазинах часто виявляють, що межа лежить у middleware безпеки, а не в самому Apache.
Реверс‑проксі та CDN: мовчазне «ні»
Навіть коли origin налаштовано правильно, edge може відмовити великому тілу:
- Політики керованого WAF можуть капати тіла запитів.
- Ingress‑контролери в контейнерних платформах часто мають дефолтні cap‑и.
- Балансувальники навантаження можуть накладати обмеження на заголовки і тіло.
Симптом зазвичай такий: локальний curl на localhost працює; зовнішнє завантаження падає з 413 або з помилкою від вендора. Логи origin чисті, бо запит ніколи не дійшов.
Особливості PHP-FPM на Ubuntu 24.04
Чотири ручки PHP, що мають значення для завантажень
upload_max_filesize: макс. розмір одного завантаженого файлу.post_max_size: макс. розмір усього тіла POST. Має бути >= розміру завантаження плюс накладні витрати.memory_limit: безпосередньо не обмежує розмір завантаження, але багато додатків читають файли в пам’ять або опрацьовують їх (ресайз зображень) і тоді це стає реальною межею.max_input_time/max_execution_time: великі завантаження на повільних лінках можуть потрапити під ці ліміти. Слід також дивитися таймаути Nginx/FPM.
Яких розмірів слід ставити?
Дійте свідомо. «Поставити все на 2G» — це не інженерія; це капітуляція.
- Встановіть
upload_max_filesizeвідповідно до бізнес‑потреб (наприклад: 128M). - Встановіть
post_max_sizeтрохи вище (наприклад: 140M–160M), бо multipart‑кодування додає накладні витрати, а форми можуть містити поля. - Встановіть ліміти веб‑сервера/проксі трохи вище за
post_max_size. - Переконайтеся, що тимчасове сховище може вмістити хоча б кілька одночасних завантажень такого розміру.
Вирівнювання таймаутів (реальність повільних завантажень)
Якщо ваші користувачі завантажують з готельного Wi‑Fi, завантаження може тривати хвилини. Це нормально. Ваш стек має погодитися чекати так довго.
- Nginx:
client_body_timeout,proxy_read_timeout(якщо проксируєте) і налаштування буферизації. - PHP‑FPM: таймаути завершення запиту (налаштування пулу можуть вбити «повільні запити»).
- Додаток: фонові процеси проти синхронної обробки.
Жарт №2: Ліміти завантажень — єдине місце, де «ще один мегабайт» може викликати інцидент у продакшн. Це план дієти для серверів, і вони теж хитрують.
Пастки на рівні додатку (фреймворки та CMS)
Якщо тіло запиту пройшло через усі перешкоди, ваш додаток все ще може відхилити файл. Саме тут команди витрачають час, бо повідомлення про помилку виглядає як «проблема сервера», хоча це бізнес‑логіка.
Правила валідації, що переважають інфраструктуру
Приклади, які ви побачите:
- Laravel валідація: правила
max:у кілобайтах. - Symfony обмеження завантаження, задані як констрейнти в одному типі форми.
- WordPress: UI «Maximum upload file size» залежить від PHP‑значень, але плагіни можуть накладати суворіші правила.
- Кастомні додатки: у конфіг файлі стоїть обмеження розміру «для безпеки», і ніхто його не оновив.
Вибухи пам’яті під час пост‑обробки
Ваш ліміт завантаження може бути 128M, але ваш додаток може потім:
- прочитати весь файл в пам’ять (
file_get_contentsна 128M файлі — це заява) - перекодувати або змінити розмір зображень (тимчасові стрибки пам’яті)
- хешувати файл у PHP‑юзерленді
Саме тоді реальна помилка — 500, «Allowed memory size exhausted», або загибель воркера. Виправлення — не «знову підняти upload_max_filesize». Виправлення — потокова обробка, чанкінг або винесення обробки поза шлях запиту.
Три корпоративні історії з «шахт» завантажень
1) Інцидент через хибне припущення: «Ми змінили php.ini, отже все виправлено»
Середній внутрішній платформенний проєкт потребував більших вкладень у вкладення рахунків. Команда оновила /etc/php/8.3/cli/php.ini на кількох серверах, протестувала CLI‑скриптом, який парсив файли, і оголосила роботу зробленою. Наступного дня веб‑інтерфейс усе ще відмовляв у прийомі файлів понад 10 MB.
Операції отримали інцидент. Логи додатка нічого корисного не показували. Канал інциденту наповнився звичним шумом: «Можливо база», «Можливо сховище», «Можливо load balancer». Ніхто не хотів сказати очевидне: зміна могла не застосуватися до рантайму, що має значення.
Справжня проблема була двоякою. По‑перше, PHP‑FPM завантажував /etc/php/8.3/fpm/php.ini, який не було змінено. По‑друге, FPM‑пул мав php_admin_value[post_max_size], встановлений на 10M від старого hardening‑спринту.
Виправлення зайняло хвилини, коли хтось перестав гадати: підтвердили SAPI, прогнали grep по перевизначеннях пулу, підняли ліміти, перезапустили FPM і верифікували через веб‑endpoint. Урок не в «будьте обережні». Урок у тому, щоб валідувати зміни на шарі, який реально торкається користувачів, а не на шарі, який вам зручніше тестувати.
2) Оптимізація, що влетіла в копієчку: «Давайте менше буферизувати для швидкості»
Команда, що мала Nginx перед PHP‑FPM, спробувала зменшити диск‑I/O, змінивши поведінку буферизації запитів. Ідея здавалася розумною: великі завантаження писалися на диск, потім PHP читав їх знову. Подвійний I/O, менше задоволення.
Вони змінили налаштування буферизації і деякі таймаути, щоб «прискорити». На тестах в LAN усе виглядало чудово. У продакшні користувачі на повільних з’єднаннях стикалися з перериваннями: завантаження помирали десь у межах 30–60 секунд. Тим часом бекенд бачив спалахи напіввідкритих з’єднань і воркерів FPM, що зависали в очікуванні.
Оптимізація відкинула тиск вгору по ланцюгу. Замість безпечної буферизації з передбачуваним використанням ресурсів, origin тримав з’єднання довше. Таймаути стали обмежувачем, модель конкурентності погіршилась: менше завершених запитів, більше in‑flight робіт, а повторні спроби лише погіршували ситуацію.
Виправлення було нудним: знову ввімкнути розумну буферизацію для endpoint‑ів завантаження, встановити чіткі ліміти розміру, вирівняти таймаути під реальні умови користувачів та проінструментувати лічильники «request aborted». Пізніше вони покращили продуктивність, але вже змінивши архітектуру (direct‑to‑object‑storage), а не намагаючись перехитрити фізику на проксі.
3) Нудна, але правильна практика, що врятувала день: «Доведіть локальне відтворення і перевірку кожного шару»
Регульований корпоративний додаток почав падати при завантаженнях після планового ребілду хостів Ubuntu 24.04. Та сама playbook, той самий Ansible, ті самі конфіги — нібито. Helpdesk повідомляв «завантаження понад 5 MB падають», що не є діагнозом, а лише сигналом лиха.
Інженер на виклику нічого не чіпав спочатку. Він відтворив проблему локально з curl до 127.0.0.1 і побачив успіх. Потім повторив той самий запит через публічний hostname і отримав 413. Це роздвоїло всесвіт на дві частини: origin працює, edge відкидає.
Далі він перевірив налаштування ingress controller і знайшов дефолтний cap на розмір запиту, що змінився з оновленням chart. Ніхто його не зафіксував, бо «дефолти підходять» — до тих пір, поки ні. Інженер підняв cap до відповідності Nginx і PHP на origin, задеплоїв і перевірив тим же curl.
Жодних героїчних вчинків. Жодних пізніх рефакторингів. Просто дисциплінований підхід: відтворити локально, порівняти шляхи, знайти перший шар, що відхиляє. Нудно — добре. Нудно масштабується.
Поширені помилки (симптом → корінь → виправлення)
1) Симптом: 413 «Request Entity Too Large» миттєво
Корінь: Nginx/Apache/proxy/WAF відхилив тіло запиту до PHP.
Виправлення: Встановіть ліміти тіла запиту на шарі, що відхилив (наприклад, Nginx client_max_body_size). Перевірте локальний vs публічний curl і подивіться журнали веб‑сервера на рядки «intended to send too large body.»
2) Симптом: «exceeds upload_max_filesize directive» у браузері/додатку
Корінь: PHP‑ліміт нижчий за розмір файлу; часто ви змінили CLI ini, а не FPM.
Виправлення: Підтвердіть шлях FPM ini через php-fpm8.3 -i, оновіть upload_max_filesize і post_max_size, перезапустіть FPM, верифікуйте через веб‑ini_get endpoint.
3) Симптом: Завантаження починається, потім падає через деякий час з 504/timeout
Корінь: Таймаути в проксі/веб‑сервері/FPM; повільні клієнти перевищують client_body_timeout або upstream‑таймаути.
Виправлення: Вирівняйте таймаути між шарами для очікуваних тривалостей завантаження; розгляньте виділені endpoint‑и з довшими таймаутами або direct‑to‑storage підхід.
4) Симптом: Випадкові збої під навантаженням; малі файли працюють, великі іноді працюють
Корінь: Тиск на тимчасовий ФС (tmpfs заповнений), брак inode або конкуренція по I/O. PHP записує тимчасові файли спочатку.
Виправлення: Моніторте використання /tmp, встановіть upload_tmp_dir на просторий ФС і враховуйте конкуруючі одночасні завантаження (N * розмір).
5) Симптом: У логах PHP показано POST Content-Length перевищує ліміт, хоча upload_max_filesize великий
Корінь: post_max_size нижчий за загальний multipart‑розмір запиту.
Виправлення: Встановіть post_max_size вище за upload_max_filesize; залиште запас для multipart‑накладних витрат і додаткових полів.
6) Симптом: 500 помилки при завантаженні великих зображень/відео
Корінь: Пост‑обробка додатка вичерпала memory_limit або час виконання. Завантаження пройшло; обробка впала.
Виправлення: Потокова обробка, черга для важкої роботи, збільшення пам’яті там, де є сенс, і обмеження розмірів у додатку відповідно до можливостей обробки.
7) Симптом: «Працює на одному сервері, але не на іншому»
Корінь: Перевизначення на рівні пулу, різна область дії vhost, відсутні include‑файли або інший проксі‑шлях.
Виправлення: Порівняйте ефективні виводи конфігів: nginx -T, grep по FPM‑пулу на php_admin_value і веб‑ini‑дамп.
Чеклісти / поетапний план
Крок за кроком: безпечно підняти ліміти завантаження на Ubuntu 24.04 (Nginx + PHP‑FPM)
- Визначте ціль: наприклад, макс. файл 128M. Рішення ґрунтуйте на потребах продукту, а не на відчуттях.
- Встановіть PHP‑ліміти:
upload_max_filesize = 128Mpost_max_size = 160M(запас)
- Перевірте перевизначення пулу: видаліть або підніміть
php_admin_valueу конфігу пулу. - Встановіть ліміт Nginx:
client_max_body_size 160m;у правильному server/location. - Перевірте тимчасове сховище: переконайтеся, що upload_tmp_dir може витримати пікову кількість одночасних завантажень.
- Перезапустіть сервіси: перезапустіть PHP‑FPM; перезавантажте Nginx після
nginx -t. - Перевірте через веб‑шлях: curl endpoint додатку або діагностичний скрипт.
- Перевірте через публічний шлях: якщо є edge/proxy, протестуйте і через нього.
- Інструментуйте і логгируйте: слідкуйте за кількістю 413, таймаутами upstream і використанням диску.
- Документуйте обрані ліміти: запишіть, де вони стоять (ini, pool, vhost, proxy). Майбутнє «ви» не екстрасенс.
Чекліст: коли НЕ треба піднімати ліміти
- Ви не можете чітко пояснити, хто потребує більших завантажень і навіщо.
- У вас немає запасу диску для тимчасових файлів.
- Ваш додаток читає весь файл в пам’ять, і ви ще не готові це виправляти.
- Ви за проксі з жорстким обмеженням, яке ви не можете змінити (отримаєте лише неконсистентну поведінку).
Чекліст: операційні запобіжники
- Встановлюйте верхні межі для кожного endpoint‑а. Не кожен POST має приймати 160 MB.
- Використовуйте окремі server/location блоки для endpoint‑ів завантаження з налаштованими лімітами/таймаутами.
- Налаштуйте алерти на частоту 413 та використання тимчасового ФС.
- Відстежуйте тривалість завантажень (p95/p99). Повільні завантаження — місце, де народжуються баги таймаутів.
Поширені запитання
1) Я змінив upload_max_filesize, але phpinfo() все ще показує старе значення. Чому?
Ймовірно, ви змінили CLI ini або неправильний FPM ini, або перевизначення пулу перемагає. Перевірте через php-fpm8.3 -i і grep по pool.d на php_admin_value.
2) Чи потрібно встановлювати і upload_max_filesize, і post_max_size?
Так. Multipart‑завантаження — це тіла POST. Якщо post_max_size менший, PHP відхилить запит ще до того, як значення файлу матиме значення.
3) Яким має бути співвідношення post_max_size до upload_max_filesize?
Більшим. Дайте запас (10–25% звичайно), бо multipart‑границі і додаткові поля збільшують загальний розмір тіла.
4) Чому я отримую 413, хоча PHP налаштований правильно?
Тому що 413 зазвичай видають Nginx/Apache/proxy/WAF. PHP запит не бачить. Перевірте логи шару, що відхиляє, і його директиви щодо розміру тіла запиту.
5) Чи потрібно робити memory_limit більшим за upload_max_filesize?
Не обов’язково для самого завантаження, оскільки PHP зберігає завантаження як тимчасові файли. Але ваш додаток може працювати з файлом у пам’яті. Якщо так — пам’ять стає реальною межею.
6) Чому завантаження падають лише в деяких користувачів?
Повільні з’єднання тригерять таймаути; корпоративні проксі можуть мати власні ліміти; мобільні мережі добре вміють робити «було б нормально» в «чому проблема». Узгодьте таймаути й тестуйте з обмеженою пропускною здатністю.
7) Чи можу я встановлювати ліміти на сайт на тому ж сервері?
Так. Використовуйте окремі PHP‑FPM пули з per‑pool php_admin_value або окремі Nginx server блоки з різними client_max_body_size. Будьте явними і документуйте це.
8) Чи покладатися на .user.ini для встановлення лімітів завантаження?
Тільки якщо вам потрібен контроль на рівні директорій і ви готові до операційної неоднозначності. Центральна конфігурація (FPM пул або ini) простіша для аудиту і менш сюрпризна під час інциденту.
9) Мої завантаження падають з «No such file or directory» в PHP. Що це означає?
Часто це тимчасова директорія: відсутня, неправильні права або повна. Встановіть upload_tmp_dir у директорію, записувану FPM‑користувачем, і переконайтеся в наявності місця.
10) Чи варто приймати великі завантаження через PHP взагалі?
Іноді так (внутрішні інструменти, низький трафік). Для високого навантаження або дуже великих файлів розгляньте direct‑to‑object‑storage з підписаними URL і асинхронну обробку.
Наступні кроки, які ви можете зробити сьогодні
- Виміряйте відмову: відтворіть з curl локально та через публічний hostname. Визначте, чи origin бачить запит.
- Закріпіть ефективний конфіг: підтвердіть значення FPM через веб‑endpoint і конфіг Nginx через
nginx -T. - Встановіть узгоджені ліміти: Nginx/proxy cap ≥ PHP
post_max_size> PHPupload_max_filesize. - Виправте тимчасове сховище: не записуйте великі тимчасові файли в майже повний tmpfs, якщо вам не подобаються випадкові відмови.
- Прийміть архітектурне рішення: якщо завантаження критичні і великі, плануйте direct‑to‑storage і тримайте PHP поза «гарячим шляхом».
Якщо зробите лише одну річ: припиніть гадати, який ini‑файл активний, і доведіть це з веб‑рантайму. Помилки завантажень процвітають в неоднозначності. Вбийте її.