Скрипт импорта товаров, страница формирования большого отчёта или плагин резервного копирования какое-то время работает, а потом обрывается с ошибкой Fatal error: Maximum execution time of 30 seconds exceeded. Это сработала настройка PHP max_execution_time. Она ограничивает время работы одного скрипта, значение по умолчанию — 30 секунд. Ниже разберём, как считается этот лимит, когда его разумно увеличить и когда лучше по-другому организовать саму задачу.
Как считается лимит
Многие думают, что это время по часам, но на Linux-серверах это не так. Согласно документации PHP, max_execution_time учитывает только время выполнения самого скрипта. Ожидание ответа базы данных, загрузка данных из внешнего API, вызовы sleep() и команды через system() в этот счёт не входят. На Windows-серверах, наоборот, считается реальное прошедшее время. Поэтому на Linux скрипт, работающий с медленным внешним API, может идти несколько минут и не получить ошибку PHP, но оборваться в другом месте.
Для скриптов, запущенных из командной строки (CLI), значение по умолчанию — 0, то есть ограничения по времени нет. Именно поэтому тяжёлые задачи через cron работают заметно стабильнее, чем страница, открытая в браузере.
Ошибка PHP и 504 Gateway Timeout — в чём разница
На долгой странице могут встретиться две разные ошибки, и их важно различать. Если в журнале есть запись «Maximum execution time», скрипт остановил сам PHP. Если в браузере появилась страница 504 Gateway Timeout, а в журнале PHP пусто, значит, веб-сервер перед PHP (например, Nginx) не дождался ответа. В Nginx за это отвечают fastcgi_read_timeout или proxy_read_timeout, по умолчанию 60 секунд. В PHP-FPM процесс может принудительно завершить параметр request_terminate_timeout. На виртуальном хостинге эти серверные настройки клиенту недоступны, поэтому даже при max_execution_time 300 секунд страница всё равно может оборваться через 60 секунд с ошибкой 504.
Способы увеличить лимит
Если задаче действительно нужно чуть больше 30 секунд, увеличение лимита — простое и правильное решение. Способы те же, что и для memory_limit.
В панели хостинга значение max_execution_time меняется в разделе настроек PHP. В ISPmanager это делается для каждого сайта отдельно; клиенты sayt.uz входят в панель со страницы хостинга в кабинете sayt.uz. Если PHP работает в режиме FPM или CGI, подойдёт файл .user.ini в папке сайта. Строку php_value в .htaccess можно использовать только в режиме модуля Apache, в остальных режимах она приведёт к ошибке 500.
; .user.ini
max_execution_time = 120
В коде используется функция set_time_limit(). У неё есть особенность: при вызове счётчик начинается заново. Если вызывать её на каждом шаге длинного цикла, скрипт фактически получает неограниченное время. Это удобно, но ошибочный цикл тоже сможет работать бесконечно, поэтому применяйте этот приём осторожно:
set_time_limit(120); // 120 секунд с этого момента
foreach ($rows as $row) {
importRow($row);
set_time_limit(30); // счётчик перезапускается на каждой строке
}
Связанный параметр — max_input_time. Он ограничивает время на приём и разбор входных данных запроса, например загружаемого файла. Именно он может мешать при загрузке большого файла по медленному каналу.
Выносим тяжёлые задачи из браузера
Если задача длится несколько минут и дольше, выполнять её через веб-страницу — неверный подход. Закрытая вкладка, обрыв связи или таймаут веб-сервера остановят её на полпути, и данные останутся записанными частично. Правильно запускать такую задачу из командной строки по cron. В ISPmanager есть отдельный раздел для заданий по расписанию, где указываются команда и время:
# запуск импорта каждый день в 03:30
30 3 * * * /usr/bin/php /var/www/example_user/data/www/example.uz/cron/import.php >> /var/www/example_user/data/logs/import.log 2>&1
Путь к PHP и к папке сайта зависит от сервера, особенно если для сайта выбрана другая версия PHP. Точный путь уточните в панели или у техподдержки. Важно и перенаправлять вывод в журнал, иначе вы не узнаете, что задача завершилась ошибкой.
Разбиение работы на порции
Если cron не подходит или задача должна запускаться по кнопке, разбейте работу на небольшие порции. Каждый запрос обрабатывает, например, 200 записей, сохраняет в базу или файл место остановки, а следующий запрос продолжает с него. JavaScript в браузере может отправлять такие запросы по очереди и показывать пользователю процент выполнения. Многие плагины WordPress для импорта, резервного копирования и оптимизации изображений работают именно так и поэтому справляются с большими объёмами даже на обычном хостинге.
Пример: оптовая компания в Намангане
Оптовая компания стройматериалов из Намангана каждый день обновляет цены на сайте misol.uz из XML-файла, выгруженного из учётной программы. Сначала менеджер нажимал кнопку «Импорт» в админке, и на файле из 8 тысяч товаров страница обрывалась с ошибкой примерно на середине. В итоге половина цен на сайте была новой, а половина — старой. Разработчик переписал импорт под запуск из CLI, поставил его в cron на 06:00 каждый день и добавил запись результата в журнал после каждых 500 товаров. Теперь импорт не зависит от лимита времени, а менеджер утром проверяет по журналу, что всё обновилось.
Какое решение выбрать
- Задача укладывается в 30–120 секунд — достаточно увеличить
max_execution_timeв панели или через.user.ini. - Задача идёт несколько минут или регулярно повторяется — переносите её в cron и CLI.
- Задача должна запускаться действием пользователя — разбивайте её на порции и выполняйте последовательными запросами.
- Страница обрывается с 504 — меняйте не настройку PHP, а архитектуру задачи.
Слишком большой лимит лишь маскирует проблему: медленный скрипт держит ресурсы сервера, а если его одновременно запустят несколько пользователей, замедлится весь сайт. Поэтому сначала полезно проверить, почему задача медленная — запросы без индексов, лишние внешние вызовы и так далее.