Каталог модулей

Когда HTTP 500 оказался не PHP и не MySQL: разбор сбоя Nginx

Когда HTTP 500 оказался не PHP и не MySQL: разбор сбоя Nginx

Утром 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:06unattended-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 и SNIDNS, браузер и внешний маршрут не были причиной
Маршрут приложенияТоварные страницы, поиск, sitemap, faviconСбой не зависел от одного контроллера OpenCart
Отдельный workerЗавершение одного процесса NginxОшибка перешла на следующий worker
Второй сайтНезависимый проект с другой PHP-цепочкойОбщим остался фронтовый слой Nginx

Два сайта дали решающий A/B-тест

Основной сайт работал по цепочке Nginx → PHP-FPM → PHP. Второй независимый проект использовал Nginx → Apache → PHP. У них были разные кодовые базы, настройки приложения и способ передачи запроса в PHP, но оба получали одинаковый HTTP 500 с одним и тем же alert Nginx.

Это резко сузило поиск. Если два независимых приложения с разными backend-цепочками падают одинаково, а база и хост стабильны, нужно исследовать самый нижний общий компонент. В этой схеме им был Nginx.

Контрольные эксперименты, отделившие симптом от причины

  • Прямой запрос к серверу. curl --resolve направил HTTPS-запрос прямо на нужный IP, сохранив домен для Host и SNI. Ошибка осталась — внешний DNS и браузер исключены.
  • Простой ресурс. HTTP 500 на /favicon.ico показал, что проблема шире сложного бизнес-маршрута.
  • Режим обслуживания. Ожидаемый 503 в maintenance-режиме и быстрый 500 в обычном пути были разными сигналами, а не одним общим «сайт упал».
  • Замена worker. После завершения одного worker alert появился в другом процессе. Причина не находилась в одном повреждённом worker.
  • Независимый проект. Та же ошибка на втором сайте значительно ослабила любую версию, связанную только с OpenCart.

Строка в apt history, которая изменила направление

Когда Nginx был определён как общий слой, история пакетного менеджера показала точное изменение: в 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 на следующий инцидент

  1. Зафиксировать время первого сбоя, точный статус, URL, Host, PID и строку журнала.
  2. Разделить внешний маршрут и сервер: выполнить прямой запрос с корректными Host и SNI.
  3. Проверить один простой ресурс и один динамический маршрут.
  4. Быстро исключить диск, память, CPU, I/O, базу и сетевую доступность.
  5. Найти второй независимый сервис на том же общем слое.
  6. Просмотреть apt history, время изменения пакетов и конфигураций.
  7. До вмешательства сохранить активную конфигурацию, версии пакетов и контрольные ответы.
  8. Выполнить одну контролируемую смену и повторить тот же набор canary-запросов.
  9. После восстановления проверить главную, админку, статический ресурс, динамический маршрут и второй сайт.

Как уменьшить риск автоматических обновлений

  • Отделить обычные security-обновления от неконтролируемой смены критичного data plane; для Nginx определить окно и canary.
  • Хранить инвентарь версий Nginx, модулей и пакетных зависимостей вместе с последней рабочей конфигурацией.
  • Иметь проверенные планы обновления вперёд и отката до того, как произойдёт авария.
  • Запускать синтетические health-checks для статического ресурса, динамического маршрута, админки и второго независимого сайта.
  • Оповещать не только о доле HTTP 500, но и о новых alerts в журнале общего proxy-слоя.
  • После смены пакета подтверждать, что трафик обслуживает ожидаемое поколение worker-процессов.

Главный вывод

Во время аварии очень легко ремонтировать то, что находится прямо перед глазами: PHP-код, SQL или страницу, которая первой попала под проверку. Но production — это цепочка зависимостей, и одинаковый симптом у двух независимых приложений часто важнее сотен строк лога одного из них.

Хорошая диагностика — не угадывание виновника. Это последовательное исключение слоёв, сохранение контрольных результатов и одна смена, эффект которой можно измерить. В этом кейсе одна строка в apt history стала решающей только потому, что вокруг неё уже была построена доказательная схема.

Краткий авторский пересказ инцидента в LinkedIn.

Nginx, HTTP 500, production, DevOps, диагностика, OpenCart

0
51
Комментарии