vvs35.ru боты, парсеры, серверы

Переехали на новый PHP — и сайт перестал принимать файлы

Сайт перевели на свежий PHP, всё открывается, работает заметно быстрее. Через сутки выясняется, что форма с вложением молча ничего не отправляет. В логах PHP пусто, в логах Apache — обрыв соединения. Виноват оказался не PHP, а директива FcgidMaxRequestLen, у которой по умолчанию стоит потолок 128 КБ.

Что было видно снаружи

Заявка без файла уходит нормально. Заявка с приложенным документом — крутится и обрывается. Ошибка при этом не показывается: браузер получает пустой ответ, скрипт формы считает это сбоем сети и молчит.

Первое подозрение всегда падает на PHP, и там всё выглядит правильно:

upload_max_filesize = 32M
post_max_size       = 32M
memory_limit        = 256M
max_execution_time  = 120

Значения щедрые, файл на 900 КБ в них помещается с большим запасом. Но до PHP этот запрос не доходит вовсе — его обрывает Apache, и в журнале ошибок остаётся строка вида mod_fcgid: HTTP request length 947 exceeds MaxRequestLen.

Откуда взялось ограничение, которого раньше не было

На старом сервере PHP работал модулем Apache. Модуль живёт внутри веб-сервера, и отдельного потолка на длину запроса у него нет — всё регулируется настройками самого PHP.

После переезда PHP запускается через mod_fcgid: веб-сервер и интерпретатор стали разными процессами, и между ними появился канал со своим ограничением. Ограничение это называется FcgidMaxRequestLen и по умолчанию равно 131 072 байтам, то есть 128 КБ. В конфигурации его никто не писал — оно просто есть.

Отсюда главный вывод, ради которого стоит читать дальше: при переезде смотреть надо не на номер версии PHP, а на схему запуска. Версия меняет язык, схема меняет правила игры.

Как понять, какая схема работает на сайте

Одна строка, положенная во временный файл в корне сайта:

<?php echo php_sapi_name();

Что означает ответ:

ОтветСхемаНа что влияет
apache2handlerмодуль внутри Apacheпотолка на длину запроса нет, но процессы тяжёлые
fpm-fcgiPHP-FPMбыстрее всего, ограничения задаются в пуле и в nginx
cgi-fcgimod_fcgid или mod_proxy_fcgiздесь и живёт FcgidMaxRequestLen
cliкомандная строкак сайту отношения не имеет, свой отдельный php.ini

Временный файл после проверки надо убрать: он рассказывает о сервере больше, чем стоит рассказывать посторонним.

Почему php.ini показывает не то, что действует

Это вторая ловушка, и она обиднее первой. Файлов php.ini на сервере несколько: свой у командной строки, свой у веб-схемы, плюс .user.ini в каталоге сайта и настройки панели хостинга поверх всего этого. Правка «того самого» php.ini через SSH меняет поведение php -v и не меняет ничего на сайте.

Действующие значения показывает только запуск тем же способом, каким работает сайт:

<?php
foreach ([
  'upload_max_filesize', 'post_max_size', 'memory_limit',
  'max_execution_time', 'max_input_vars', 'max_file_uploads',
  'default_socket_timeout', 'output_buffering',
] as $k) {
  printf("%-24s %s\n", $k, ini_get($k));
}

На одном из переездов такая сверка показала, что три директивы из восьми разъехались молча: в файле стояло одно, действовало другое. Ни одна из них не роняла сайт заметно — они меняли поведение под нагрузкой, и это худший вид поломки, потому что находится он позже всех.

Что чинит саму загрузку файлов

Директива ставится в конфигурации Apache, не в php.ini, и требует перезапуска, а не перечитывания конфигурации:

<IfModule mod_fcgid.c>
    FcgidMaxRequestLen 33554432
</IfModule>

33 554 432 байта — это 32 МБ, ровно столько же, сколько разрешает upload_max_filesize. Держать эти два числа согласованными важнее, чем сделать их большими: если канал уже, чем разрешает PHP, пользователь получает молчаливый обрыв вместо понятного «файл слишком большой».

Чем на самом деле объясняется «стало быстрее в пять раз»

После переезда сайт действительно открывается заметно быстрее, и это легко записать в заслугу новой версии языка. Обычно дело не в ней.

Кратный рост дают две вещи: переход от порождения процесса на каждый запрос к постоянно живущим процессам и включённый кэш опкода. Номер версии добавляет свою долю, но она меньше, чем принято думать.

Практический смысл этой поправки простой: «стало в пять раз быстрее, чем было» — это не то же самое, что «выжато всё». Пока не сверены действующие значения директив и не проверен кэш опкода, работа не закончена, даже если заказчик уже доволен цифрой.

Порядок проверки после переезда

  1. Узнать схему запуска через php_sapi_name().
  2. Снять действующие значения восьми директив через ini_get(), а не глазами по файлу.
  3. Сверить их со значениями на старом сервере — построчно, а не «в целом похоже».
  4. Для схем на fcgid выставить FcgidMaxRequestLen согласованно с post_max_size.
  5. Проверить, включён ли кэш опкода: opcache.enable и opcache.memory_consumption.
  6. Прогнать руками все формы с вложениями — файлом заведомо крупнее 128 КБ.
  7. Убрать временные файлы проверок из корня сайта.

Чего этот список не покажет

Он не ловит ошибки самого кода на новой версии языка: убранные функции, изменившееся поведение при сравнении типов, предупреждения, ставшие ошибками. Для этого нужен отдельный прогон и чтение логов под настоящим трафиком, а не после переезда в тишине.

И он ничего не говорит о базе данных. Смена версии PHP часто идёт вместе со сменой версии MySQL или MariaDB, и там свои сюрпризы — строгий режим и режимы сортировки на первом месте.

Похожая история про то, как настройка по умолчанию оказывается настоящей причиной сбоя, — разбор ошибки 525 у Cloudflare, где виноваты 768 соединений, поставленные без нашего участия. И про сжатие, которое работало только для HTML, потому что список типов в конфиге был закомментирован.

Коротко: частые вопросы

Почему после перехода на PHP 8 перестали загружаться файлы?

Если сайт работает через mod_fcgid, размер запроса ограничивает директива FcgidMaxRequestLen, а не upload_max_filesize. По умолчанию она равна 131 072 байтам — 128 КБ. Всё, что тяжелее, обрывается на уровне Apache, до того как запрос дойдёт до PHP, поэтому в логах PHP не остаётся ничего.

Как узнать, по какой схеме запускается PHP на сайте?

Функция php_sapi_name() возвращает имя схемы: apache2handler — модуль Apache, fpm-fcgi — PHP-FPM, cgi-fcgi — mod_fcgid или mod_proxy_fcgi, cli — командная строка. Номер версии PHP об этом не говорит ничего, а от схемы зависят и скорость, и ограничения на размер запроса.

Почему php.ini показывает не те значения, которые действуют?

Файлов php.ini на сервере обычно несколько — свой у CLI, свой у веб-схемы, плюс .user.ini и настройки панели хостинга. Действующие значения показывает phpinfo() или ini_get(), запущенные тем же способом, каким работает сайт. На одном из переездов так разошлись три директивы из восьми.

Что значит «сайт стал быстрее в пять раз» после смены версии PHP?

Обычно это сравнение с тем, что было, а не с тем, что возможно. Кратный рост чаще даёт переход с CGI на постоянный процесс и включённый кэш опкода, а не сам номер версии. Пока не сверены действующие значения директив, считать работу законченной рано.

Переезжаете на новый PHP или уже переехали и что-то ведёт себя странно? Сниму действующие значения, сверю со старым сервером и покажу, что разъехалось. Диагностика бесплатная, отчёт остаётся у вас.

Настройка сервера от 2 000 ₽

Читать дальше