Вранці 20 серпня 2026 року один із production-серверів почав поводитися нестабільно: сайти то відкривалися, то відповідали 500 Internal Server Error. Одночасно збоїв phpMyAdmin. За такої картини першими під підозру природно потрапляють PHP, MariaDB, навантаження або нещодавня зміна коду. Проте цього разу причина була в іншому шарі.
Від першого збою до повного відновлення production минуло близько 7,5 години з урахуванням пауз і паралельної роботи з підтримкою. За цей час вдалося виключити дані та застосунки, відтворити помилку без DNS і клієнтської мережі, перевірити другий незалежний сайт і пов’язати аварію зі зміною пакета Nginx, встановленою автоматичним оновленням Ubuntu.
Що було відомо на старті
Симптом не був прив’язаний до однієї сторінки. HTTP 500 з’являвся на різних URL, включно з простими запитами на кшталт /favicon.ico. У журналі Nginx повторювався сигнал:
no buffer space in script copy
Ця строка одразу вказувала на Nginx, але сама по собі ще не доводила конкретну директиву, модуль або внутрішній дефект. Тому замість підгонки фактів під першу версію було важливо визначити межі збою.
| Час | Подія | Значення для розслідування |
|---|---|---|
| До 06:06 | Сайти працювали штатно | Є базова точка нормальної роботи |
| 06:06 | unattended-upgrade оновив пакет Nginx з 1.18.0-6ubuntu14.18 до .19 | Зафіксовано зміну спільного інфраструктурного шару |
| Після 07:18 | У журналі масово з’явилися alerts, а запити почали завершуватися HTTP 500 | Часова близькість посилила гіпотезу про регресію або несумісність |
| Під час діагностики | Той самий симптом підтвердився на другому незалежному сайті | Код одного проєкту перестав бути спільним знаменником |
Після оновлення до .20 | Сайти, адміністративні частини та phpMyAdmin відновилися | Контрольована зміна усунула симптом у всьому контурі |
Чому перевірка бази даних була правильною
Те, що причина виявилася не в MariaDB, не робить початкову перевірку бази помилкою. Одночасний збій phpMyAdmin і повідомлення панелі керування про неможливість запису даних могли означати проблему сховища, бази або проміжного шару.
Гіпотезу перевірили фактами: сервер MariaDB відповідав, SQL напряму через окремий клієнт виконувався, завислих запитів не було, а процеси бази не демонстрували аварійного стану. Після цього базу даних перестали змінювати. Це важливе правило: після достатнього спростування шару не треба продовжувати його «лікувати».
Діагностика за шарами
Замість хаотичної заміни компонентів система перевірялася зверху вниз. Кожен тест мав не просто зібрати ще один лог, а прибрати з дерева причин цілий клас гіпотез.
| Шар | Що перевірили | Висновок |
|---|---|---|
| Ресурси хоста | Вільне місце, CPU, I/O, vmstat | Загальне виснаження ресурсів не підтвердилося |
| MariaDB | Доступність, прямий SQL, список процесів | База приймала й виконувала запити |
| Клієнтська мережа | Прямий запит на IP сервера з правильним Host/SNI | DNS, браузер і зовнішній маршрут не були причиною |
| Маршрут застосунку | Товарні сторінки, пошук, sitemap, favicon | Збій не залежав від одного контролера OpenCart |
| Окремий worker | Завершення одного процесу Nginx | Помилка перейшла на наступний worker |
| Другий сайт | Незалежний проєкт з іншим PHP-ланцюжком | Спільним залишився фронтовий шар Nginx |
Два сайти дали вирішальний A/B-тест
Основний сайт працював за ланцюжком Nginx → PHP-FPM → PHP. Другий незалежний проєкт використовував Nginx → Apache → PHP. Вони мали різний код, конфігурацію застосунку та спосіб передавання запиту до PHP, але отримували однаковий HTTP 500 і той самий alert.
Це різко звузило пошук. Якщо два незалежні застосунки з різними backend-ланцюжками падають однаково, а база та хост стабільні, треба досліджувати найнижчий спільний компонент. У цій схемі ним був Nginx.
Контрольні експерименти, які відділили симптом від причини
- Прямий запит до сервера.
curl --resolveнаправив HTTPS-запит безпосередньо на потрібний IP, зберігши домен для Host і SNI. Помилка залишилася — зовнішній DNS та браузер виключено. - Простий ресурс. HTTP 500 на
/favicon.icoпоказав, що проблема ширша за складний бізнес-маршрут. - Режим обслуговування. Очікуваний 503 у maintenance-режимі та швидкий 500 у звичайному шляху були різними сигналами, а не одним «падінням сайту».
- Заміна worker. Після завершення одного worker alert з’явився в іншому процесі. Отже, причина не була локальною пам’яттю одного завислого процесу.
- Другий проєкт. Однакова помилка на незалежному сайті остаточно послабила версію про код конкретного OpenCart.
Рядок в apt history, який змінив напрямок
Коли спільний шар було визначено, журнал пакетного менеджера показав точну зміну: о 06:06 Ubuntu автоматично оновила Nginx з ревізії .18 до .19. Це не було автоматичним доказом вини пакета, але дало сильну часову гіпотезу, узгоджену з усіма вже зібраними фактами.
df -h
vmstat 1
mysqladmin ping
nginx -V
dpkg-query -W 'nginx*' 'libnginx*'
grep -R "upgrade nginx" /var/log/apt/history.log*
curl --resolve example.com:443:SERVER_IP https://example.com/favicon.ico
У наступному інциденті перевірку останніх системних змін варто виконувати раніше — одразу після появи alert із конкретного інфраструктурного компонента. Це не замінює діагностику, але швидше формує правильний список гіпотез.
Чому не варто було одразу робити downgrade
На production-сервері з панеллю керування, кількома сайтами, TLS-конфігураціями, додатковими модулями Nginx і різними backend-ланцюжками поспішний відкат може створити нову несумісність. Пакет — це не один бінарний файл: важливі залежності, модулі, конфігурація та покоління worker-процесів.
Тому спочатку було зібрано докази, а потім виконано контрольоване оновлення до доступної ревізії 1.18.0-6ubuntu14.20. Після цього одразу відновилися обидва сайти, адміністративні частини та phpMyAdmin, а масові HTTP 500 зникли. Саме така кореляція «одна зміна — одночасне відновлення всіх контурів» суттєво посилює RCA.
Що RCA доводить, а що залишає відкритим
| Підтверджено | Не підтверджено окремим відтворенням |
|---|---|
| Збій був спільним для двох незалежних сайтів | Конкретна директива Nginx, яка запускала помилку |
| MariaDB, диск, CPU та I/O не пояснювали симптом | Точний внутрішній шлях коду або конкретний CVE |
| Nginx сам повертав 500 у прямому тесті | Чи була причина чистою регресією пакета або взаємодією з конфігурацією/модулем |
| Початок збою слідував за автоматичною зміною пакета | Універсальна відтворюваність на будь-якому сервері з цією ревізією |
| Наступна ревізія усунула симптом у всьому контурі | Можливість безпечно повернути попередню ревізію без побічних ефектів |
Коректне формулювання причини таке: production-збій був локалізований у спільному шарі Nginx, виник після автоматичного оновлення пакета до ревізії .19 і припинився після контрольованого переходу на .20. Точний внутрішній механізм потребує окремого лабораторного відтворення.
Короткий playbook на наступний інцидент
- Зафіксувати час першого збою, точний статус, URL, Host, PID і рядок журналу.
- Розділити зовнішній маршрут і сервер: виконати прямий запит із правильними Host та SNI.
- Перевірити один простий ресурс і один динамічний маршрут.
- Швидко виключити диск, пам’ять, CPU, I/O, базу та мережеву доступність.
- Знайти другий незалежний сервіс на тому самому спільному шарі.
- Переглянути
apt history, час зміни пакетів і конфігурацій. - Перед втручанням зберегти конфігурацію, версії пакетів і контрольні відповіді.
- Виконати одну контрольовану зміну та повторити той самий набір canary-запитів.
- Після відновлення перевірити не лише головну сторінку, а й адмінку, статичний ресурс, динамічний маршрут і другий сайт.
Як зменшити ризик автоматичних оновлень
- Розділити security-оновлення та автоматичну зміну критичного data plane; для Nginx визначити контрольоване вікно й canary.
- Зберігати інвентар версій Nginx, модулів і пакетних залежностей разом із робочою конфігурацією.
- Мати перевірений план переходу вперед і відкату, а не вигадувати його під час аварії.
- Запускати синтетичні health-checks для статичного ресурсу, динамічного маршруту, адміністративного контуру й другого незалежного сайту.
- Сповіщати не тільки про відсоток HTTP 500, а й про нові alerts у журналі спільного проксі-шару.
- Після пакетної зміни підтверджувати, що запити обслуговує нове покоління worker-процесів.
Головний висновок
Під час аварії дуже легко ремонтувати те, що знаходиться прямо перед очима: PHP-код, SQL або конкретну сторінку. Але production — це ланцюжок залежностей, і однаковий симптом у двох незалежних застосунках часто важливіший за сотні рядків логу одного з них.
Хороша діагностика — не вгадування винуватця. Це послідовне виключення шарів, фіксація контрольних результатів і одна зміна, наслідок якої можна перевірити. У цьому кейсі один рядок в apt history став корисним лише тому, що до нього вже була побудована доказова схема.