Истекло время ожидания ответа от сервера: причины и починка
Разбираем таймауты сайта: где стоит лимит, кто его выбирает, как найти тяжёлый запрос и что делать, если тормозит не сайт, а хостинг.
Содержание
- Таймаут это не одна ошибка, а четыре разных
- Первые пятнадцать минут: собрать факты
- Логи, в которых лежит ответ
- Кто обычно виноват на сайте услуг
- Что крутить в лимитах и когда это лечение симптома
- Внешним запросам нужен свой таймаут
- Когда виноват не код, а тариф
- Чек-лист при таймауте
- Что делать прямо сейчас
- Частые вопросы
Таймаут это не одна ошибка, а четыре разных лимита: браузер обрывает примерно через 30 секунд, nginx по умолчанию через 60, PHP по max_execution_time через 30, база по своим настройкам. Чинят не увеличением лимитов, а поиском того, что именно упирается.
Клиент пишет в чат: страница крутится минуту, потом браузер выдаёт, что истекло время ожидания ответа сервера. Через полчаса всё само проходит, к вечеру повторяется, а с телефона вообще открылось нормально. Это не одна поломка, а целое семейство: свой лимит держит браузер, свой nginx, свой php-fpm и свой MySQL. Ниже разберу, как за двадцать минут понять, чей именно таймаут сработал, какие цифры собрать до звонка хостеру и почему в половине случаев крутить конфиг бесполезно.

Веду абонентку шесть лет, таймауты разбираю чаще всех остальных ошибок вместе взятых. Причина всегда одна из шести, но владельцу сайта их не видно: он видит только белую страницу и крутилку.
Таймаут это не одна ошибка, а четыре разных
Между кликом клиента и ответом сайта стоит цепочка из четырёх звеньев, и у каждого свой лимит терпения. Кто первым сдался, такой текст ошибки вы и увидите.
Браузер. Ждёт установки соединения, а не генерации страницы. Chrome покажет ERR_CONNECTION_TIMED_OUT или ERR_TIMED_OUT примерно через 30 секунд, если сервер вообще не отвечает на порт. Это сетевой уровень: упал веб-сервер, забанил фаервол, кончились свободные соединения.
nginx. Отдал запрос в PHP и ждёт. По умолчанию fastcgi_read_timeout и proxy_read_timeout стоят 60 секунд. Не дождался, вернул 504 Gateway Timeout. Страница ошибки при этом красивая, серверная, отдаётся мгновенно.
php-fpm. Убивает свой же скрипт по max_execution_time (по умолчанию 30 секунд) или по request_terminate_timeout. Клиент получает белый экран, 500 или обрывок недорисованной страницы. В логе остаётся строка Maximum execution time of 30 seconds exceeded.
MySQL. Держит wait_timeout, innodb_lock_wait_timeout (обычно 50 секунд) и лимит одновременных соединений. Отваливается по-своему: MySQL server has gone away или Lock wait timeout exceeded; try restarting transaction.
| Что видит клиент | Кто оборвал | Типичный лимит | Куда смотреть |
|---|---|---|---|
| Страница не открылась вообще, ошибка сети | Браузер, до сервера не дошло | 30 с | ping, telnet на 443, статус хостинга |
| 504 Gateway Timeout | nginx | 60 с | error.log nginx, строка upstream timed out |
| Белый экран или обрыв на середине | php-fpm | 30-120 с | php-errors.log, slow log |
| Ошибка соединения с базой | MySQL | 50 с | лог MySQL, список процессов |
| Открывается, но 15-40 секунд | Никто не оборвал, просто медленно | нет | замеры TTFB |
Последняя строка важнее, чем кажется. Сайт, который отвечает за 25 секунд, формально работает и мониторинг доступности его считает живым. Клиенты при этом уходят: терпение среднего посетителя заканчивается на восьмой секунде.
Первые пятнадцать минут: собрать факты
До того как лезть в конфиги, нужны цифры. Один запрос curl даёт разбивку по этапам и сразу отсекает половину гипотез.
curl -o /dev/null -s -w "dns: %{time_namelookup}nconnect: %{time_connect}ntls: %{time_appconnect}nttfb: %{time_starttransfer}ntotal: %{time_total}ncode: %{http_code}n" https://example.ru/
Как читать результат:
connectбольше 2 секунд или запрос висит на этой стадии: проблема сетевая, до PHP дело не дошло. Смотрим фаервол, лимит процессов, статус хостинга.tlsбольшой, а connect быстрый: сервер перегружен настолько, что не успевает даже рукопожатие.ttfb20-60 секунд: страница генерируется, но медленно. Виноват код, база или внешний запрос.code 504при быстром connect: nginx не дождался PHP, лимит известен.
Дальше сравниваем динамику со статикой. Возьмите файл стилей темы и обычную страницу:
curl -o /dev/null -s -w "статика: %{time_starttransfer}n" https://example.ru/wp-content/themes/mytheme/style.css
curl -o /dev/null -s -w "главная: %{time_starttransfer}n" https://example.ru/
curl -o /dev/null -s -w "поиск: %{time_starttransfer}n" "https://example.ru/?s=test"
Статика отдаётся за 80 миллисекунд, а главная за 30 секунд: сервер жив, тормозит PHP. Статика тоже тормозит: упёрлись в диск, в сеть или в лимиты аккаунта. Главная быстрая, а поиск виснет: нашли конкретную тяжёлую страницу, дальше копать проще.
Ещё один замер, который экономит часы споров: то же самое ночью. Прогоните эти команды в 3:00 и в 13:00. Если ночью 200 миллисекунд, а днём 12 секунд при неизменном коде, значит проблема в соседях по серверу или в лимитах тарифа, и никакая оптимизация плагинов её не вылечит.
Логи, в которых лежит ответ
На shared-хостинге логи обычно в домашней папке аккаунта, у Timeweb это /home/c/cXXXXX/example.ru/logs/, у Beget ~/example.ru/logs/. На VPS стандартно /var/log/nginx/ и /var/log/php8.2-fpm.log.
grep -i "upstream timed out" ~/logs/nginx-error.log | tail -20
grep -c "Maximum execution time" ~/logs/php-errors.log
tail -100 ~/logs/php-errors.log | grep -i "fatal|timeout|memory"
Строка про upstream timed out содержит и URL, и время. Если все обрывы приходятся на /wp-admin/admin-ajax.php или на /?s=, вы уже знаете виновника без всякого профилировщика.
Самый полезный инструмент при таймаутах называется slow log. Он пишет не факт обрыва, а стек вызовов зависшего процесса. В пуле php-fpm добавляем:
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/example-slow.log
request_terminate_timeout = 120s
После перезапуска пула в логе появятся дампы: каждая запись показывает, на какой функции скрипт провёл больше пяти секунд. В моей практике это чаще всего curl_exec, file_get_contents, wp_remote_post и mysqli_query. Первые три означают внешний сервис, четвёртая означает базу. На shared доступа к пулу нет, но многие панели умеют включать slow log сами, а лог медленных запросов MySQL там обычно есть в разделе баз данных.
Кто обычно виноват на сайте услуг
За шесть лет список повторяющихся причин почти не меняется.
Внешний API без таймаута. Форма отправляет заявку в CRM, курсы валют тянутся с сайта банка, виджет доставки стучится в СДЭК. Партнёр прилёг, ваш скрипт ждёт его до последнего. Одна такая интеграция на главной странице кладёт весь сайт: каждый посетитель занимает воркер PHP на 60 секунд.
wp-cron на каждом запросе. WordPress по умолчанию дёргает планировщик при заходе посетителя. Если в очереди висит задача рассылки или проверки обновлений, невезучий посетитель ждёт её выполнения. Лечится переводом на системный крон: константа DISABLE_WP_CRON в wp-config.php плюс задание в панели хостинга раз в 15 минут.
Плагин, который пишет в свою таблицу без индексов. Счётчики просмотров, логгеры, антиспам-журналы. Первый год всё летает, на третий в таблице три миллиона строк и каждый просмотр страницы делает по ней полный перебор. Как искать конкретного виновника, разбирал отдельно в статье про то, какой плагин тормозит сайт.
Боты и парсеры на поиске. Внутренний поиск и фильтры каталога это самые дорогие страницы сайта. Пара сканеров, которые перебирают /?s= с разными словами, съедают весь пул процессов. В access.log это видно за минуту: сотни запросов в час с одного адреса на страницы с параметрами.
Бэкап по расписанию в час пик. UpdraftPlus, Duplicator и штатные модули умеют архивировать сайт в полдень. Архивация упирается в диск и CPU, сайт на эти двадцать минут превращается в тыкву. Переносим на 4:00 и половина жалоб исчезает.
Синхронная отправка почты. Форма шлёт письмо через внешний SMTP прямо в момент отправки. SMTP-сервер отвечает медленно, посетитель видит крутилку тридцать секунд и уходит, будучи уверенным, что заявка не ушла. Она ушла, просто он этого не дождался.
Что крутить в лимитах и когда это лечение симптома
Поднять лимит иногда правильно, а иногда это способ растянуть агонию. Ориентир такой: лимит поднимают под операцию, которая обязана быть долгой, а не под страницу, которую видит клиент.
| Параметр | Где живёт | Значение по умолчанию | Когда поднимать |
|---|---|---|---|
| max_execution_time | php.ini, .user.ini | 30 с | Импорт, выгрузка, миграция. Для фронта нет |
| memory_limit | php.ini, wp-config | 128M | Тяжёлый импорт, каталог от 5000 позиций |
| fastcgi_read_timeout | конфиг nginx | 60 с | Только вместе с php-лимитом, иначе бесполезно |
| request_terminate_timeout | пул php-fpm | 0 (без ограничения) | Всегда ставить чуть выше nginx, чтобы висяки убивались |
| pm.max_children | пул php-fpm | 5 | По формуле: память пула делить на 60-80 МБ |
| max_input_vars | php.ini | 1000 | Большие формы, настройки темы, конструкторы меню |
Для админских операций правильнее поднимать лимит точечно, а не для всего сайта:
// в файле импорта или в mu-plugin, только для консольного запуска
if ( defined( 'WP_CLI' ) && WP_CLI ) {
set_time_limit( 900 );
ini_set( 'memory_limit', '512M' );
}
Главная ловушка shared-хостинга: увеличенный таймаут делает хуже. Зависший запрос держит воркер PHP. При pm.max_children = 5 пять зависших запросов означают, что шестой посетитель уже стоит в очереди, и сайт лежит целиком, хотя формально ошибок в коде нет. С лимитом в 30 секунд очередь рассасывается сама, с лимитом в 300 секунд сайт стоит пять минут. Поэтому на фронте лимит лучше держать низким, а долгие операции уносить в консоль или в фон.
Внешним запросам нужен свой таймаут
Это то место, где одна строчка кода экономит несколько часов диагностики. Никогда не выпускайте запрос наружу без явного лимита ожидания.
$response = wp_remote_post( 'https://api.partner.ru/v1/lead', [
'timeout' => 5,
'body' => $payload,
'headers' => [ 'Content-Type' => 'application/json' ],
] );
if ( is_wp_error( $response ) ) {
error_log( '[partner] ' . $response->get_error_message() );
// заявка уже лежит в базе, отправим повторно кроном
wp_schedule_single_event( time() + 300, 'my_retry_lead', [ $lead_id ] );
}
Что важно знать про значения по умолчанию. У wp_remote_post таймаут 5 секунд, и это разумно. У file_get_contents работает default_socket_timeout из php.ini, а там обычно 60. У голого curl без CURLOPT_TIMEOUT ожидание не ограничено вообще: скрипт будет ждать, пока его не убьёт php-fpm. Именно необрамлённый curl в чужом плагине я нахожу чаще всего, когда сайт зависает через раз.
Порядок правильный такой: сначала записать заявку к себе в базу, потом пытаться отдать её наружу, при неудаче поставить повтор в очередь. Клиент видит страницу благодарности через полсекунды независимо от того, жив ли партнёрский API.
Когда виноват не код, а тариф
Бывает, что оптимизировать уже нечего: запросы быстрые, плагины проверены, а сайт всё равно раз в день выпадает в таймаут. Тогда смотрим на лимиты аккаунта.
На shared-хостинге считают CPU (обычно 1-2 ядра с ограничением по процентам), память на аккаунт, количество одновременных процессов PHP и число подключений к MySQL. При превышении хостер не отдаёт ошибку, он ставит запросы в очередь, и снаружи это выглядит ровно как таймаут. В панели такие события видны как CPU limit reached или превышение entry processes.
Как доказать цифрами, а не ощущениями:
- Замерить TTFB главной ночью и в пик, по десять замеров, взять медиану.
- Снять график нагрузки в панели за неделю и наложить на время жалоб.
- Посчитать, сколько запросов в секунду реально приходит:
wc -lпо access.log за час, поделить на 3600. - Проверить, что при том же коде на тестовой копии у другого хостера цифры другие.
Если ночью 250 миллисекунд, а днём 8 секунд при 3 запросах в секунду, это не ваш сайт тяжёлый, это тариф маленький или сосед по серверу шумный. Про то, как читать эти лимиты и что за ними стоит, подробно писал в разборе лимитов shared-хостинга по CPU и памяти, а про нормальные значения времени ответа в материале про TTFB больше 600 мс.
Переезд на VPS решает проблему, но не всегда нужен. Разница в деньгах небольшая: приличный shared 250-600 рублей в месяц, VPS с двумя ядрами и 4 ГБ памяти 900-1800 рублей. Разница в другом: VPS надо администрировать, и если этим никто не занимается, через год вы получите тот же таймаут, только на непропатченном сервере. Обслуживание такого сервера обычно уже входит в абонентку, у меня это годовой и помесячный тарифы.
Чек-лист при таймауте
Порядок, по которому я иду сам. Первые четыре пункта занимают минут пятнадцать и в большинстве случаев уже дают ответ.
- Проверить, у всех ли: сервис доступности, мобильный интернет, чужая сеть.
- Снять curl с таймингами для статики, главной и подозрительной страницы.
- Посмотреть nginx error.log на строки upstream timed out, собрать список URL.
- Посмотреть php-errors.log на Maximum execution time и Allowed memory size.
- Открыть панель хостинга, проверить графики CPU, память, число процессов за сутки.
- Включить slow log php-fpm и лог медленных запросов MySQL, подождать повтора.
- Отключить подозрительный плагин на тестовой копии, повторить замер.
- Проверить все внешние интеграции на наличие явного таймаута в коде.
- Перенести бэкапы и тяжёлые задачи на ночь, wp-cron перевести на системный.
- Настроить мониторинг не только доступности, но и времени ответа, с порогом в 3 секунды.
Десятый пункт я бы поставил первым, если бы не приходилось сначала тушить пожар. Мониторинг с проверкой времени ответа ловит проблему на стадии «стало медленно» за неделю до того, как клиенты начнут видеть ошибку. Как это настроить бесплатно, разбирал в статье про мониторинг доступности сайта.
Что делать прямо сейчас
Если сайт выдаёт таймаут в эту минуту, сделайте три вещи по порядку. Снимите curl с таймингами и сохраните вывод. Загляните в error.log и найдите последние двадцать строк. Проверьте в панели хостинга, не упёрлись ли в лимит процессов. С этими тремя фактами разговор с хостером или с подрядчиком займёт десять минут вместо двух дней переписки в стиле «у нас всё работает».
Если разбираться некогда, напишите мне через форму на главной и приложите то, что успели собрать: адрес страницы, время появления ошибки, скриншот. Обычно я нахожу источник за час-два, потому что список подозреваемых короткий и почти всегда один и тот же. Другие разборы по скорости лежат в рубрике про скорость.
Частые вопросы
Что значит истекло время ожидания ответа сервера
Что запрос не получил ответа за отведённое время. Кто именно оборвал соединение, зависит от лимита, который сработал первым: браузер, веб-сервер, PHP или база данных.
Что смотреть в первую очередь
Лог ошибок PHP и лог веб-сервера за время инцидента. Там видно, оборвался ли скрипт по своему лимиту, ждал ли сервер ответа от PHP или запрос вообще не дошёл.
Помогает ли увеличить лимиты
Как временная мера да, как решение нет. Если страница собирается 50 секунд, поднятие лимита до минуты просто отодвигает обрыв. Искать надо тяжёлый запрос или внешний вызов без своего тайм-аута.
Почему проблема плавающая
Потому что упирается в нагрузку: днём соседи по shared-хостингу забирают процессор, ночью нет. Плавающий таймаут почти всегда означает, что вы на пределе тарифа, а не что в коде ошибка.