IT-инфраструктура • инструкции • практикаПоиск по сайту

Серверы и системное администрирование

Найти материал →

Time_wait в linux: почему заканчиваются порты и как исправить проблему с Tcp-соединениями

Когда TIME_WAIT становится проблемой: почему заканчиваются порты и как исправить ситуацию

При эксплуатации высоконагруженных сервисов на Linux инженеры нередко сталкиваются с ошибкой `Cannot assign requested address`, внезапными разрывами TCP-соединений и резким ростом числа сокетов в состоянии `TIME_WAIT`. На первый взгляд кажется, что проблема связана с нехваткой памяти или неисправностью сетевого оборудования. Однако часто причина находится в архитектуре приложения и настройках TCP-стека Linux.

TIME_WAIT: что это и почему он появляется

TCP-соединение не исчезает сразу после вызова `close()`. Одна из сторон переходит в состояние `TIME_WAIT` - обычно примерно на 60 секунд. Такой механизм нужен для защиты от повторной доставки старых пакетов и корректного завершения соединения. Если прежние сегменты задержались в сети, у них должно быть время покинуть инфраструктуру до того, как комбинация IP-адреса и порта будет использована повторно.

Сама по себе такая логика совершенно нормальна. Проблемы начинаются, когда приложение постоянно открывает и закрывает большое количество коротких соединений. Например, PHP-FPM может выполнять отдельный `connect()` к Redis или внешнему API для каждого запроса, не используя пул или постоянное подключение.

Предположим, сервис создаёт 1000 TCP-соединений в секунду. При длительности `TIME_WAIT` около 60 секунд в системе может одновременно находиться до 60 тысяч таких сокетов. При этом диапазон эфемерных портов, заданный параметром `ip_local_port_range`, нередко содержит лишь около 28 тысяч значений. В результате свободные порты заканчиваются, а приложение получает ошибку `Cannot assign requested address`.

Тем, кто ищет ответ на вопрос, TIME_WAIT что это и как уменьшить, важно понимать: большое количество таких сокетов не всегда означает неисправность ядра. В первую очередь это сигнал о слишком частом создании TCP-соединений и недостаточно эффективной работе приложения с сетевыми ресурсами. Подробный разбор причин и практических решений представлен в материале о [настройке TCP-соединений и TIME_WAIT](https://habr.com/ru/companies/otus/articles/1059554/?utm_campaign=1059554&utm_source=habrahabr&utm_medium=rss).

Почему не стоит бездумно включать tcp_tw_recycle

Первой реакцией на большое число `TIME_WAIT` иногда становится попытка активировать параметры `tcp_tw_recycle` и `tcp_tw_reuse`. Такой совет до сих пор встречается в старых руководствах, но применять его по шаблону опасно.

Параметр `tcp_tw_recycle` удалён из Linux начиная с версии ядра 4.12. Причина связана с несовместимостью механизма с NAT и сетями, где множество клиентов используют один публичный IP-адрес. При активных TCP-временных метках ядро ожидает, что значения timestamp от одного источника будут строго возрастать.

В реальной инфраструктуре это условие часто нарушается. Например, мобильный оператор может обслуживать тысячи устройств через один NAT-шлюз, который динамически распределяет порты. Пакеты от разных клиентов получают разные временные метки и могут приходить в другом порядке. Сервер с включённым механизмом агрессивного переиспользования способен принять нормальный пакет за повтор старого сегмента и молча отбросить его. Пользователь увидит случайный обрыв соединения, а диагностика окажется особенно сложной.

`tcp_tw_reuse` менее опасен, но его возможности ограничены. Он относится главным образом к исходящим соединениям, когда сервер выступает клиентом, и требует поддержки TCP timestamps. Для входящих подключений к серверному порту этот параметр не решает проблему.

Архитектурное решение: пул соединений

Прежде чем менять параметры ядра, необходимо проверить код приложения. Сценарий "одно подключение на один запрос" часто становится главным источником лавинообразного роста `TIME_WAIT`. Поэтому ответ на вопрос, как избавиться от TIME_WAIT в Linux, обычно начинается не с `sysctl`, а с отказа от лишних подключений.

Для Redis в PHP вместо `$redis->connect()` можно использовать `$redis->pconnect()`. Постоянное соединение сохраняется между запросами и не создаётся заново для каждой операции.

В Python следует создать общий экземпляр `ConnectionPool` из библиотеки `redis-py` и передавать его клиентам, а не формировать новый пул при каждом обращении. В Node.js разумно использовать один клиент `ioredis`, настроив keep-alive и корректное повторное подключение.

Аналогичный принцип применяется к базам данных, HTTP-клиентам, очередям сообщений и внутренним API. Веб-сервису лучше поддерживать ограниченное число долгоживущих соединений, чем постоянно расходовать эфемерные порты на новые TCP-сеансы.

Что проверить в Linux

Если архитектура уже оптимизирована, а нагрузка всё равно высока, стоит последовательно изучить сетевые параметры:

- диапазон `ip_local_port_range`;
- число сокетов в `TIME_WAIT`;
- значения `ulimit -n`;
- параметры очередей и backlog;
- наличие NAT, прокси и балансировщиков;
- реальную скорость создания и закрытия соединений.

Для диагностики применяют `ss -s`, `ss -tan state time-wait`, `netstat` в старых системах, а также инструменты наблюдаемости и захват трафика. Важно оценивать не только общее количество сокетов, но и распределение соединений по адресам назначения. Если все подключения направлены к одному сервису, вероятен дефицит локальных портов; если адресатов много, причины могут быть иными.

Настройка TCP TIME_WAIT Linux должна проводиться только после измерений. Увеличение диапазона локальных портов может дать дополнительный запас, но не заменит пул соединений. Аналогично, повышение лимита файловых дескрипторов полезно лишь тогда, когда приложение и операционная система действительно готовы обслуживать больше открытых сокетов.

Согласование с сетевой инфраструктурой

Локальная настройка сервера не должна рассматриваться отдельно от всей цепочки прохождения трафика. Балансировщик, reverse proxy, NAT-шлюз или firewall могут иметь собственные таблицы состояний и ограничения на число одновременных соединений.

Если сервер активно переиспользует подключения, а промежуточный прокси закрывает их через короткий интервал, ожидаемого эффекта не будет. Поэтому необходимо согласовать тайм-ауты keep-alive, параметры балансировщика и настройки клиентских библиотек. Для HTTP-сервисов полезны HTTP/2 и HTTP/3, поскольку они позволяют передавать множество запросов через меньшее количество транспортных соединений.

Когда возникает ситуация "много соединений TIME_WAIT как исправить", универсального значения `sysctl` не существует. Иногда достаточно включить keep-alive, иногда требуется изменить пул соединений, расширить диапазон портов или перераспределить нагрузку между несколькими исходящими IP-адресами.

Почему опасны старые рецепты

Не стоит копировать настройки из инструкций, написанных для устаревших версий Linux. Особенно это касается советов включить `tcp_tw_recycle`, безусловно уменьшить таймеры или очистить таблицу соединений принудительно. Такие действия способны замаскировать симптом, но привести к потерям пакетов, нестабильности и труднообъяснимым отказам клиентов за NAT.

Настоящая оптимизация TCP-соединений и TIME_WAIT строится на нескольких уровнях: корректной архитектуре приложения, повторном использовании соединений, разумной настройке портов, контроле файловых дескрипторов и согласовании тайм-аутов всей сетевой цепочки. Изменения ядра должны быть последним, а не первым шагом.

Главный вывод прост: `TIME_WAIT` - не ошибка и не вредный режим, который нужно любой ценой устранить. Это штатный механизм TCP. Опасным он становится тогда, когда приложение создаёт короткие соединения быстрее, чем система успевает освобождать ресурсы. В большинстве случаев проблему решают пул соединений, keep-alive и правильная диагностика нагрузки, а не агрессивное вмешательство в работу ядра.

Прокрутить вверх