WordPress не може завантажити зображення: швидке усунення неполадок з дозволами, лімітами та бібліотеками

Було корисно?

Ви натискаєте Додати нове в Бібліотеці медіа. Індикатор обертання крутиться. Потім WordPress відповідає: «HTTP error», «Unable to create directory», або відбувається мовчазний збій, коли нічого не відбувається, але тиск крові зростає.

Це одна з тих проблем, що виглядає як «WordPress робить WordPress», доки не згадаєш, що насправді це стек: браузер → CDN/WAF → вебсервер → PHP-FPM → файловa система → бібліотеки для зображень → база даних. Трюк — знайти перший шар, який вас вводить в оману.

Швидкий план діагностики (робіть у вказаному порядку)

1) Підтвердіть точну помилку на краю і на оріджині

  • Вкладка Network у інструментах розробника браузера: шукайте 413, 403, 500, 502, 504.
  • Логи оріджина: Nginx/Apache + PHP-FPM логи на той самий часовий штамп.
  • Повідомлення WordPress часто нечітке. Ваші логи — ні.

2) Доведіть, що каталог uploads записуваний користувачем рантайму

  • Перевірте власника і режим wp-content/uploads.
  • Спробуйте створити файл від імені користувача вебсерверу. Не вгадуйте.

3) Перевірте ліміти по всьому ланцюжку (перемагає найменший)

  • PHP: upload_max_filesize, post_max_size, memory_limit, max_execution_time.
  • Вебсервер: Nginx client_max_body_size; Apache LimitRequestBody.
  • Проксі/CDN/WAF: 413, обмеження тіла запиту, правила безпеки.

4) Перевірте стан тимчасового каталогу

  • Права /tmp, місце, доступні inode, опції монтування.
  • PHP upload_tmp_dir і шляхи сесій.

5) Перевірте бібліотеки обробки зображень

  • Відсутність Imagick/GD може перетворити валідні завантаження на «HTTP error» під час генерування мініатюр.
  • Шукайте фатальні помилки в PHP-логах.

6) Застосування політик безпеки (тільки після базових перевірок)

  • SELinux/AppArmor відмови виглядають як проблеми з правами, але chmod 777 цього політику не виправить.
  • ModSecurity може блокувати multipart-запити через загальні правила.

Правило великого пальця: якщо завантаження відразу падає — підозрюйте ліміти/WAF. Якщо воно чекає і потім падає — підозрюйте PHP-обробку, тимчасовий простір, бібліотеки, таймаути.

Що насправді відбувається, коли завантаження не вдаються

Завантаження у WordPress — це не просто «скопіювати файл на диск». Типовий шлях виглядає так:

  1. Браузер надсилає multipart POST на /wp-admin/async-upload.php (або через REST ендпоїнти залежно від версії/шляху редактора).
  2. Край/ CDN/ WAF інспектує розмір запиту і тип вмісту, може завершити запит раніше.
  3. Вебсервер приймає тіло запиту; може накласти власні обмеження на максимальний розмір тіла і таймаути.
  4. PHP отримує файл у тимчасове місце ($_FILES), потім переміщує його до wp-content/uploads/YYYY/MM.
  5. WordPress створює метадані вкладення, генерує мініатюри і може виконувати операції Imagick/GD.
  6. База даних отримує рядки для post-вкладення + метаданих; файловa система отримує оригінал і похідні розміри.

Це багато рухомих частин. Коли одна ламається, WordPress часто повідомляє це як невизначений слоган: нечітко, але технічно не неправильно.

Дві практичні спостереження:

  • Якщо файл ніколи не потрапляє до uploads, дивіться раніше: ліміти, права, тимчасові файли, WAF.
  • Якщо оригінал потрапляє, але мініатюри ні — дивіться на Imagick/GD, memory_limit або таймаути.

Одна перефразована ідея (мислення про надійність): ефективність інструментів спостереження робить складні системи раптово зрозумілими (перефразована ідея).

Жарт №1: повідомлення про помилку в Бібліотеці медіа нагадує сповіщення пейджера «щось сталося». Дякую, WordPress. Дуже корисно.

Цікаві факти та контекст (те, що пояснює дивні явища)

  • Пайплайн завантаження PHP — це старомодно: файли спочатку тимчасово зберігаються, потім переміщуються. Тимчасовий простір — реальна залежність, а не деталь реалізації.
  • «HTTP error» став універсальною відповіддю: історично WordPress іноді показував загальні помилки завантаження, коли реальна виняткова ситуація була під час генерації мініатюр, а не в HTTP-шарі.
  • За замовчуванням налаштування Imagick відрізняються по дистрибутах: файли політик ImageMagick можуть обмежувати пам’ять, диск або формати; одна й та сама інсталяція WordPress може поводитись по-різному на різних хостах.
  • GD проти Imagick — це не просто вподобання: GD може бути швидшим для дрібних операцій, Imagick краще обробляє більше форматів і забезпечує вищу якість, але важчий і чутливіший до політик ресурсів.
  • Обмеження Nginx на розмір запиту — явне: на відміну від деяких систем, client_max_body_size в Nginx — це жорсткий фільтр, що може швидко повертати 413.
  • Apache може приховати ліміт у багатьох місцях: LimitRequestBody може бути віртуальним хостом, директорією або глобальним конфігом; «але я змінив» не є доказом.
  • Хмарні та контейнерні файлові системи змінюють режими відмов: overlay-файлові системи і ефермерні диски роблять «завантаження працюють до деплою» дивно поширеним випадком інцидентів.
  • EXIF-орієнтація ускладнила справу: деякі робочі процеси обертають зображення під час обробки; відсутність підтримки EXIF може призводити до невідповідностей «в мене на ноуті працює».

Практичні завдання: команди, виводи, рішення

Це «зробіть зараз» завдання. Кожне містить: команду, реалістичний вивід та рішення, яке ви приймаєте. Виконуйте їх на хості WordPress (або контейнері), що фактично обробляє PHP.

Завдання 1: Визначте вебсервер і користувача рантайму PHP

cr0x@server:~$ ps -eo user,comm | egrep 'nginx|apache2|httpd|php-fpm' | head
root     nginx
www-data nginx
root     php-fpm8.2
www-data php-fpm8.2

Що це означає: працюють Nginx і PHP-FPM, а воркер-користувач — www-data.

Рішення: усі тести запису в файлову систему повинні виконуватись як www-data. Якщо це хост з cPanel, ви можете побачити nobody або per-vhost користувачів; не робіть припущень.

Завдання 2: Підтвердіть шлях WordPress і знайдіть uploads

cr0x@server:~$ sudo find /var/www -maxdepth 4 -type f -name wp-config.php 2>/dev/null
/var/www/site/wp-config.php

Що це означає: корінь WordPress ймовірно /var/www/site.

Рішення: зосередьте перевірки на /var/www/site/wp-content/uploads, якщо не встановлено кастомну константу UPLOADS.

Завдання 3: Перевірте власність і права на uploads

cr0x@server:~$ ls -ld /var/www/site/wp-content/uploads
drwxr-xr-x 12 root root 4096 Dec 27 09:40 /var/www/site/wp-content/uploads

Що це означає: uploads належить root:root. Це класичний випадок «після деплою все працювало як root, а потім у продакшені — ні».

Рішення: змініть власника на користувача/групу рантайму (або на групу деплою, яка його включає). Уникайте 777 як «виправлення».

Завдання 4: Доведіть записуваність, створивши файл від імені рантайм-користувача

cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/site/wp-content/uploads/.writetest && echo ok'
touch: cannot touch '/var/www/site/wp-content/uploads/.writetest': Permission denied

Що це означає: PHP теж не може там писати.

Рішення: виправте власність/ACL; ще не чіпайте PHP-ліміти.

Завдання 5: Безпечно виправте власність (one-liner, потім повторно протестуйте)

cr0x@server:~$ sudo chown -R www-data:www-data /var/www/site/wp-content/uploads
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/site/wp-content/uploads/.writetest && echo ok'
ok

Що це означає: рантайм тепер може писати.

Рішення: повторіть завантаження у WordPress. Якщо все ще не вдається, переходьте до лімітів/логів.

Завдання 6: Перевірте вільне місце на диску (так, серйозно)

cr0x@server:~$ df -h /var/www/site/wp-content/uploads /tmp
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2   40G   39G  220M  99% /
tmpfs           2.0G  1.9G  120M  95% /tmp

Що це означає: і root-файлова система, і /tmp майже заповнені. Завантаження можуть не встигнути підготуватись або завершитись.

Рішення: звільніть місце негайно або розширте томи; також почистіть тимчасові накопичення. Якщо ви тільки виправите права uploads, проблема все одно буде періодично повторюватись.

Завдання 7: Перевірте вичерпання inode («диск повний, але df бреше»)

cr0x@server:~$ df -i /var/www/site/wp-content/uploads /tmp
Filesystem       Inodes  IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2  2621440 2621201     239  100% /
tmpfs            524288  523901     387  100% /tmp

Що це означає: у вас закінчилися inode. Шторм маленьких файлів (кеші, сесії, тимчасові файли) викликає це.

Рішення: почистіть каталоги з великою кількістю звертань (кеш/сесії/tmp). Довгостроково: перемістіть кеші з кореневого FS, змініть політику зберігання або збільшіть файлову систему з більшою кількістю inode.

Завдання 8: Перевірте власне PHP-ліміти, що діють

cr0x@server:~$ php -i | egrep 'upload_max_filesize|post_max_size|memory_limit|max_execution_time|upload_tmp_dir'
upload_max_filesize => 2M => 2M
post_max_size => 8M => 8M
memory_limit => 128M => 128M
max_execution_time => 30 => 30
upload_tmp_dir => no value => no value

Що це означає: завантаження понад 2 MB будуть відхилені; також WordPress часто потребує більше пам’яті для обробки великих JPEG, а тимчасовий каталог використовується за замовчуванням.

Рішення: установіть upload_max_filesize і post_max_size відповідно до ваших потреб, і розгляньте підвищення memory_limit, якщо обробка зображень не вдається.

Завдання 9: Перевірте ліміт розміру запиту у Nginx

cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'client_max_body_size'
45:    client_max_body_size 2m;

Що це означає: Nginx блокує тіла понад 2 MB до того, як PHP їх побачить.

Рішення: підвищте client_max_body_size у правильному контексті (http/server/location) і перезавантажте Nginx.

Завдання 10: Перезавантаження Nginx і підтвердження валідності конфігурації

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

Що це означає: зміни вживі без скидання з’єднань.

Рішення: повторіть завантаження файлу трохи понад попередній ліміт, щоб підтвердити, що ворота піднято.

Завдання 11: Перевірте конфіг PHP-FPM на предмет пер-пул переписувань

cr0x@server:~$ sudo egrep -R 'php_admin_value\[upload_max_filesize\]|php_admin_value\[post_max_size\]|php_admin_value\[memory_limit\]' /etc/php/8.2/fpm/pool.d
/etc/php/8.2/fpm/pool.d/www.conf:php_admin_value[upload_max_filesize] = 2M
/etc/php/8.2/fpm/pool.d/www.conf:php_admin_value[post_max_size] = 8M

Що це означає: ви можете вічно редагувати php.ini, і нічого не зміниться, бо пул його переопреділяє.

Рішення: оновіть налаштування пулу і перезапустіть PHP-FPM.

Завдання 12: Перезапустіть PHP-FPM і стежте за помилками

cr0x@server:~$ sudo systemctl restart php8.2-fpm
cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager -l | sed -n '1,12p'
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled)
     Active: active (running) since Fri 2025-12-27 09:52:21 UTC; 2s ago
       Docs: man:php-fpm8.2(8)

Що це означає: сервіс здоровий після зміни.

Рішення: якщо він не стартує — ваша конфігурація невірна; відкотіть і знову застосуйте уважно.

Завдання 13: Трейл логів вебсерверу під час відтворення проблеми

cr0x@server:~$ sudo tail -f /var/log/nginx/error.log
2025/12/27 09:54:10 [error] 1241#1241: *392 client intended to send too large body: 7340032 bytes, client: 203.0.113.10, server: site.example, request: "POST /wp-admin/async-upload.php HTTP/1.1", host: "site.example"

Що це означає: це однозначно Nginx request size, а не WordPress.

Рішення: збільшіть client_max_body_size і переконайтесь, що ви редагували правильний server block (не дефолтний, який ви не використовуєте).

Завдання 14: Трейл PHP-FPM логів на предмет фатальних помилок обробки

cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[27-Dec-2025 09:55:37] WARNING: [pool www] child 2219 said into stderr: "PHP Fatal error:  Uncaught Error: Call to undefined function imagecreatefromjpeg() in /var/www/site/wp-includes/class-wp-image-editor-gd.php:92"

Що це означає: функції GD відсутні; генерація мініатюр падає після завантаження, часто показуючись як «HTTP error».

Рішення: встановіть/увімкніть відповідне PHP-розширення (наприклад, php-gd) і перезапустіть PHP-FPM.

Завдання 15: Перевірте встановлені PHP-розширення (GD, Imagick)

cr0x@server:~$ php -m | egrep -i 'gd|imagick|exif'
exif

Що це означає: EXIF присутній; GD і Imagick — ні.

Рішення: встановіть хоча б одну бібліотеку для зображень (GD — найпростіший варіант; Imagick потужніший, але потребує ImageMagick і налаштувань). Не працюйте без жодної.

Завдання 16: Перевірте режим SELinux і останні відмови

cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ sudo ausearch -m avc -ts recent | tail -n 3
type=AVC msg=audit(1735293382.612:1187): avc:  denied  { write } for  pid=2311 comm="php-fpm" name="uploads" dev="dm-0" ino=420112 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=dir permissive=0

Що це означає: SELinux заблокував запис; права можуть бути коректні, але ярлики контекстів — ні.

Рішення: виправте контексти (наприклад, призначте правильний httpd_sys_rw_content_t для uploads) замість ослаблення SELinux.

Права та власність: причина №1

Якщо ви виконаєте одну перевірку — нехай це буде: чи може користувач рантайму PHP створювати файли в wp-content/uploads?

Як виглядає «правильно»

  • Каталог uploads належить користувачу/групі рантайму вебу (www-data на Debian/Ubuntu; apache на багатьох RHEL-системах), або записуваний через групу/ACL.
  • Права директорій зазвичай 0755 або 0775. Файли зазвичай 0644.
  • Якщо ви використовуєте deploy-користувача і тримаєте код лише для читання в рантаймі, то uploads має бути окремим записуваним шляхом з явними правами.

Чого не слід робити

Не «виправляйте» це командою chmod -R 777 wp-content/uploads. Ви міняєте простий збій на інцидент безпеки. На мульти-тенант хостингу це — подарунок для латерального руху.

Коли власність повертається до root

Це відбувається коли:

  • Скрипти деплою запускаються як root і копіюють дерево uploads з власністю root.
  • Резервні копії відновлюються з неправильною мапою uid/gid (особливо між хостами).
  • Контейнери перебудовують томи і змінюють права при маунті.

Рекомендація політики: трактуйте uploads як персистентні дані, а не код. Керуйте ними як даними. Резервуйте, монтуйте, маркуйте, моніторте.

Ліміти розміру: PHP, вебсервер, проксі та CDN

Завантаження відмовляються на найменшому ліміті в ланцюжку. Потрібен лише один скупий компонент, щоб зіпсувати всім день.

PHP-ліміти, що мають значення

  • upload_max_filesize: максимальний розмір одного завантаженого файлу.
  • post_max_size: максимальний розмір усього тіла POST (має бути >= upload_max_filesize і реально більший для оверхеду multipart).
  • memory_limit: обробка зображень може вимагати множини від стисненого розміру файлу. 12MB JPEG може значно розгорнутись у пам’яті.
  • max_execution_time: повільне зберігання або CPU-важкі трансформації можуть вичерпати час виконання.
  • max_input_time: може мати значення для повільних завантажень.

Обмеження Nginx та Apache

  • Nginx client_max_body_size — найпоширеніша причина 413.
  • Apache LimitRequestBody та налаштування проксі можуть накладати обмеження; також перевіряйте таймаути, коли PHP за проксі модулем.

Проксі і CDN

Навіть якщо оріджин ідеальний, CDN/WAF може відхиляти великі POST або multipart тіла, або накладати ліміти, залежні від тарифного плану. Якщо завантаження падають лише через публічний домен, але проходять при зверненні до оріджина напряму (тест із приватної мережі), винен край.

Жарт №2: Десь у стеці завжди живе 2MB ліміт з 2009 року, тихо виконуючи свій обов’язок.

Тимчасові каталоги та вичерпання inode/диску

Завантаження спочатку потрапляють у тимчасовий каталог. Якщо тимчасовий простір заповнений, має неправильні права, змонтований з noexec дивними способами або це занадто маленький tmpfs — ви побачите помилки, схожі на проблеми з правами, HTTP-помилки або таймаути.

Типові підписи відмов

  • Малі файли проходять, великі падають: тимчасовий простір (або його ліміт) тісний.
  • Перервні відмови під час піку трафіку: вичерпання inode тимчасового каталогу, шлях сесій або пікові навантаження конкурентності.
  • Завантаження проходять, але обробка падає: тимчасовий простір у порядку; проблеми з бібліотеками або пам’яттю.

Практичні поради

  • Не монтуйте /tmp як маленький tmpfs, якщо ви не розрахували його на завантаження і сплески.
  • Якщо ви в контейнерах — явно змонтуйте персистентний том для uploads і достатньо великий scratch для тимчасових файлів.
  • Стежте за використанням inode. Це тихий вбивця на сайтах з великою кількістю кеш-файлів.

Бібліотеки для зображень: Imagick проти GD і чому «HTTP error» іноді бреше

WordPress зазвичай намагається створити кілька похідних розмірів під час завантаження. Це означає, що потрібен робочий бекенд редактора зображень. Якщо ні Imagick, ні GD не працездатні, завантаження можуть «провалитися» після передачі, під час генерації метаданих.

Як обирати для продакшену

  • GD: найпростіший ланцюг залежностей, хороший базовий варіант. Встановіть PHP-розширення — і зазвичай все працює.
  • Imagick: більш здатний, краще обробляє деякі формати і операції, але вимагає ImageMagick плюс політики і ресурсні обмеження, які можуть здивувати.

Типові режими відмов бібліотек

  • Відсутнє розширення: GD/Imagick не встановлені для тієї версії PHP, що обслуговує запити.
  • Неправильний PHP SAPI: ви встановили php-gd для CLI, але FPM використовує іншу версію; CLI-тести проходять, веб — ні.
  • Політики ImageMagick: операції відкидаються, пам’ять/диск обмежені, формат заблокований.
  • Вичерпання пам’яті: фатальні помилки при створенні великих мініатюр.

Якщо сумніваєтесь — тримайте відкритим лог PHP-FPM під час відтворюваного завантаження. Якщо бачите фатали в класах редактора зображень, припиняйте звинувачувати WordPress і лагодьте рантайм.

Шари безпеки: SELinux/AppArmor, правила WAF, ModSecurity

Системи безпеки відмовляють «правильно», тобто вони відмовляють у спосіб, що виглядає як поломка додатку. Їхня робота — блокувати. Ваше завдання — швидко це підтвердити.

SELinux: права, які не є правами

На системах із SELinux контексти файлів мають таке ж значення, як UNIX-моди. Ви можете поставити 0777 і все одно отримати відмову. Якщо бачите AVC denys для uploads — виправляйте контексти директорії і тримайте SELinux у режимі enforcing.

AppArmor

AppArmor може обмежити PHP-FPM або вебсервер у записі до певних шляхів. Симптом може бути ідентичним до випадку з неправильними правами. Перевіряйте профілі і логи, якщо ви на Ubuntu з увімкненими політиками AppArmor.

ModSecurity та правила WAF

Multipart-запити — улюблена ціль загальних правил. False positives трапляються. Якщо ви отримуєте 403 при завантаженнях, але звичайні адмін-сторінки працюють — перевірте логи WAF щодо заблокованих правил на async-upload.php або REST-ендпоїнтах.

Типові помилки: симптом → корінь проблеми → виправлення

«Unable to create directory wp-content/uploads/2025/12. Is its parent directory writable by the server?»

  • Корінь проблеми: неправильна власність на wp-content/uploads, відсутній біт виконування на батьківській директорії, або відмова SELinux.
  • Виправлення: встановіть правильну власність/режим; перевірте запис як користувач рантайму; для SELinux — встановіть правильний контекст на uploads.

«HTTP error» одразу при завантаженні

  • Корінь проблеми: 413 — запит занадто великий від Nginx/Apache/CDN, або відмова WAF.
  • Виправлення: підніміть ліміти тіла запиту по всьому шляху; підтвердіть через логи; тимчасово обійдіть край для ізоляції.

Завантаження завершується, але зображення не з’являється у Бібліотеці медіа

  • Корінь проблеми: фатал під час генерації метаданих; помилка запису в базу; права заважають фінальному переміщенню з тимчасового каталогу.
  • Виправлення: тримайте відкритими PHP-логи; перевірте підключення до БД; перевірте тимчасовий каталог і uploads; перевірте PHP-розширення.

Лише великі зображення падають, малі проходять

  • Корінь проблеми: upload_max_filesize, post_max_size, client_max_body_size, або тимчасовий простір/пам’ять.
  • Виправлення: збільшіть ліміти; перевірте тимчасовий диск; підніміть memory_limit для обробки.

Завантаження працюють на одному сервері, але не на іншому (той самий код)

  • Корінь проблеми: відсутній GD/Imagick, інша політика ImageMagick, різний стан SELinux/AppArmor або інша проксі-конфігурація.
  • Виправлення: порівняйте php -m, значення php.ini, дампи конфігурацій Nginx/Apache і налаштування безпеки.

Завантаження іноді падають після деплою

  • Корінь проблеми: крок деплою скидає права uploads або міняє маунти томів; контейнери втрачають ефермерні uploads.
  • Виправлення: тримайте uploads як персистентний том; забезпечте власність в деплої; додайте перевірку здоров’я, що пише в uploads.

Завантаження падають з 500 або 502 після тривалої паузи

  • Корінь проблеми: таймаут PHP-FPM, таймаут upstream, вичерпання пам’яті, повільне зберігання або важкі операції Imagick.
  • Виправлення: перевірте таймаути upstream; підніміть PHP-час виконання; профілюйте I/O; оптимізуйте розміри зображень; подумайте про винесення обробки зображень.

Чеклісти / покроковий план

Чекліст A: «Потрібно виправити за 10 хвилин»

  1. Відтворіть помилку один раз з відкритими devtools. Зафіксуйте код статусу і відповідь.
  2. Трейл Nginx/Apache error лог; трейс PHP-FPM логи одночасно.
  3. Перевірте власність wp-content/uploads і тест запису як користувач рантайму.
  4. Перевірте використання диску і inode для uploads і тимчасових шляхів.
  5. Перевірте активні ліміти тіла для PHP і вебсерверу.
  6. Підтвердіть, що GD або Imagick встановлені для тієї версії PHP, що обслуговує запити.
  7. Якщо SELinux — перевірте AVC denys перш ніж робити щось ризиковане.

Чекліст B: «Зробити так, щоб більше не траплялося»

  1. Моніторинг: диск, inode та використання /tmp з алертами.
  2. Гігієна деплою: переконайтесь, що ваш пайплайн ніколи не міняє власність персистентних uploads несподівано.
  3. Конфіг як код: зберігайте PHP-FPM override-и і Nginx-ліміти у версійованому репозиторії.
  4. Паритет бібліотек: стандартизуйте набір розширень між середовищами; включайте їх у образи.
  5. Політика на краю: документуйте і тестуйте ліміти тіла у WAF/CDN; додавайте ендпоїнти завантажень у allowlist за потреби.
  6. Операційний тест: додайте синтетичну перевірку, що завантажує маленьке зображення і перевіряє створення похідних версій (у безпечному тестовому шляху).

Чекліст C: перевірки для інженера збереження

  1. Підтвердіть, що uploads живуть на надійному сховищі, а не на ефермерному диску контейнерів.
  2. Підтвердіть, що файлова система підтримує ваш робочий навантаження: багато дрібних файлів, часті створення директорій, іноді великі записи.
  3. Підтвердіть, що бекап/відновлення зберігає відображення uid/gid (або маєте post-restore fixup).
  4. Підтвердіть латентність I/O під навантаженням; повільні диски викликають таймаути, що виглядають як «помилки WordPress».

Три міні-історії з продакшену

1) Інцидент, спричинений хибним припущенням

Міграція здавалася простою: перенести WordPress з застарілого VM на новий хост, зберегти ту саму структуру директорій, відновити бекап, переключити DNS. Команда вважала, що якщо сторінки завантажуються — складна частина виконана.

За кілька хвилин маркетологи спробували завантажити зображення для запуску продукту. Завантаження падали з «Unable to create directory». Хтось виконав звичні дії: перезапустив PHP-FPM, почистив кеші, пересохранив постійні посилання. Нічого не допомагало. Сайт «працював», тож всі думали, що проблема — в WordPress.

Реальна причина була прозаїчною: бекап відновили як root, створивши wp-content/uploads з власністю root:root. На старому хості інша конфігурація маскувала це з допомогою ліберальних ACL. На новому хості рантайм був www-data і міг читати, але не писати.

Це зайняло більше часу, ніж могло, бо перша реакція — налаштовувати PHP-ліміти. Це приклад хибного припущення: «завантаження падають — отже розмір». Виправлення — одно-рядкова chown і доданий post-restore крок, що явно встановлює власність на записувані директорії і перевіряє це записом як рантайм-користувач.

Урок: не сприймайте «сайт завантажується» як успіх. Для WordPress «можна завантажувати медіа» — це критична продакшен-функціональність, а не «приємна особливість».

2) Оптимізація, що відкотилась назад

Команда, що прагнула кращої продуктивності, захотіла прискорити все. Вони перенесли /tmp на tmpfs, щоб зменшити I/O та прискорити PHP сесії і завантаження. На папері — елегантно: пам’ять швидша за диск, і це тимчасові файли.

Деякий час все було чудово. Швидкість сторінок стабілізувалась, і графіки латентності диску покращились. Потім команда контенту завантажила пачку високорезолюційних зображень — саме те, що трапляється перед кампанією.

Завантаження почали випадково падати. Не всі, але достатньо, щоб бути нестерпними. Логи PHP показували «failed to write file to disk», в той час як ОС на перший погляд «в порядку», бо фізичний диск мав місце.

Проблема була проста: tmpfs був недостатньо об’ємним, і одночасні завантаження плюс генерація мініатюр створили сплески використання тимчасового простору. Tmpfs заповнився, inode вичерпались, а стадія завантаження PHP провалилася. Виправлення не полягало у відмові від tmpfs, а в його правильному розмірі, моніторингу і переміщенні важких тимчасових робіт на окремий scratch-том.

Оптимізації, що прибирають вузькі місця — чудово. Оптимізації, що створюють приховані вузькі місця — причина, чому ви пояснюєте керівництву, чому «швидше» зробило сайт непридатним.

3) Нудна, але правильна практика, що врятувала день

Інша організація運увала WordPress у великому масштабі з частими оновленнями контенту. Вони були не захоплюючими людьми — вони маркували кабелі і шанували вікна змін. Чудово.

У них був стандартний «health check uploads» в пайплайні деплою. Він робив дві речі: перевіряв, чи може рантайм-користувач писати в uploads, і виконував невелике завантаження через PHP, що підтверджувало генерацію метаданих без фаталів. Це займало секунди.

Одного дня патч ОС вніс тонку зміну: PHP-FPM перезапустився і завантажив інший порядок ini-директорій, фактично втративши GD-розширення у тому оточенні. Сайт все одно обслуговував сторінки. Адмін-логіни працювали. Але завантаження зображень падали б, коли прийшла контент-команда.

Пайплайн виявив це негайно і заблокував деплой. Інженери встановили відсутнє розширення для правильної версії PHP, перезапустили сервіс і знову виконали перевірку здоров’я. Ніякого простою. Ніяких панік. Ніяких «chmod 777».

Ось магія нудної правильності: вона не запобігає всім відмовам, але запобігає принизливим інцидентам.

Часті питання

1) Чому WordPress каже «HTTP error», коли права файлів неправильні?

Тому що ендпоїнт завантаження може падати на різних стадіях, і WordPress іноді агрегує їх в одне загальне повідомлення. Перевірте логи вебсерверу і PHP для реальної причини.

2) Я підняв upload_max_filesize, але завантаження все одно падають. Що я пропустив?

Зазвичай одне з наступного: post_max_size менший, Nginx client_max_body_size менший, в PHP-FPM пулі є override з меншими значеннями, або CDN/WAF має нижчий ліміт.

3) Потрібні обидва GD і Imagick?

Ні. Потрібен хоча б один робочий бекенд для обробки зображень. GD простіше тримати послідовним. Imagick нормальний, якщо ви керуєте політиками ImageMagick і ресурсами.

4) Завантаження працює, але мініатюри не генеруються. Що перевірити?

Бібліотеки для зображень і обмеження пам’яті/часу. Трейте PHP-FPM логи під час завантаження. Відсутність gd/imagick або вичерпання пам’яті — часті випадки.

5) Чи може «диск повний» виглядати як проблема з правами?

Так. PHP може не вміти записати тимчасові файли, і WordPress може погано сигналізувати про це. Перевірте df -h і df -i для uploads і тимчасових шляхів.

6) Яка найбезпечніша модель прав для uploads?

Тримайте код лише для читання в рантаймі, якщо можливо, і зробіть wp-content/uploads записуваним для користувача/групи рантайму (зазвичай через групову власність і 0775, або через ACL). Уникайте глобального запису.

7) Чому воно падає лише через публічний домен, але працює при локальному тесті?

Ваш край (CDN/WAF/load balancer) накладає обмеження або блокує multipart-запити. Порівняйте коди статусу і логи. Тестуйте, обходячи край для ізоляції.

8) Як зрозуміти, чи SELinux — проблема?

Якщо SELinux у режимі enforcing і ви бачите AVC denys, що стосуються php-fpm або вебсерверу при спробі запису до uploads — це SELinux. Виправте контексти; не «вирішуйте» відключенням SELinux.

9) Чому контейнеризовані завантаження WordPress зникають після redeploy?

Тому що uploads записуються у файлову систему контейнера, яка ефермерна. Змонтуйте персистентний том для wp-content/uploads і тримайте його як дані.

Висновок: кроки, що запобігають повторенню

Якщо WordPress не може завантажити зображення, найшвидший шлях — дисциплінований скептицизм. Ігноруйте UI-повідомлення. Довіряйте логам. Доведіть записуваність. Потім переслідуйте ліміти. Потім — бібліотеки. Тільки після цього звинувачуйте політику безпеки.

Зробіть наступне

  1. Додайте перевірку здоров’я uploads (тест запису + перевірка створення похідних версій) у деплоях.
  2. Уніфікуйте конфіг рантайму (PHP-ліміти, Nginx/Apache ліміти, PHP-FPM pool overrides) і тримайте в version control.
  3. Моніторьте зберігання серйозно: диск, inode і використання /tmp з оповіщеннями.
  4. Виберіть стратегію бібліотек (GD як базова або Imagick з явною політикою) і зробіть її консистентною між середовищами.
  5. Документуйте ліміти на краю, щоб «працює в staging» не стало сюрпризом, коли підключається CDN.

Завантаження — це критичний продакшен-пайплайн. Трактуйте їх як такий, і Бібліотека медіа перестане дурити вашу on-call ротацію.

← Попередня
10 міфів про GPU, які не вмирають
Наступна →
CSS Grid vs Flexbox: правила вибору та рецепти макетів, що витримують продакшен

Залишити коментар