Выберите продукт

Linux TCP: TIME_WAIT и CLOSE_WAIT — диагностика через ss и настройка sysctl

Если на сервере копятся TIME_WAIT или CLOSE_WAIT, «плывут» веб/прокси и базы: растут задержки, заканчиваются порты и file descriptors. Показываю, как быстро диагностировать через ss, найти виновника по PID/порту и какие sysctl трогать безопасно.
Linux TCP: TIME_WAIT и CLOSE_WAIT — диагностика через ss и настройка sysctl

TIME_WAIT и CLOSE_WAIT — два TCP-состояния, из-за которых админы чаще всего видят «аномальные» цифры в ss, ловят «Too many open files», внезапные 502/504 на прокси или просто рост задержек. Важно: сами по себе эти состояния не всегда проблема. Проблема начинается, когда их становится слишком много, и вы упираетесь в лимиты ядра, диапазон ephemeral-портов, backlog очередей и file descriptors.

Ниже — практический разбор: что означает каждое состояние, как быстро снять картину командой ss -s, как найти «виновника» по процессу/порту, и какие sysctl действительно имеют смысл (включая net.ipv4.tcp_fin_timeout, net.core.somaxconn, net.ipv4.tcp_tw_reuse), а какие лучше не трогать без понимания последствий.

Быстрый чек-лист: это норма или инцидент

Короткая ориентация по типовым симптомам:

  • Много TIME_WAIT — часто следствие высокой скорости коротких соединений (HTTP без keep-alive, активные health-check, сканеры, очень «разговорчивые» клиенты). Для нагруженных edge/API это может быть нормой, но на «клиентских» узлах легко упереться в ephemeral-порты.
  • Много CLOSE_WAIT — почти всегда утечка соединений в приложении: удалённая сторона уже закрыла соединение, а ваш процесс не закрыл сокет. Это лечится кодом/библиотекой/таймаутами, а не «тюнингом ядра».
  • Ошибки «Too many open files» — упёрлись в file descriptors (лимиты процесса или системы). Частая причина — растущий CLOSE_WAIT или слишком много активных соединений.
  • Сбои на входе (accept errors, 502/504 на прокси, «не коннектится») — смотрим очереди (backlog), somaxconn, SYN backlog и лимиты сервиса.

Как снять «снимок TCP» через ss

Начинайте с агрегированной статистики:

ss -s

В выводе интересны общее число TCP-сокетов и распределение по состояниям (например, estab, timewait, orphaned).

Дальше — быстрый подсчёт по ключевым состояниям:

ss -ant state time-wait | wc -l
ss -ant state close-wait | wc -l
ss -ant state established | wc -l

Чтобы понять, какие локальные порты «генерируют» TIME_WAIT или где копится CLOSE_WAIT:

ss -ant state time-wait | awk '{print $4}' | awk -F: '{print $NF}' | sort | uniq -c | sort -nr | head
ss -ant state close-wait | awk '{print $4}' | awk -F: '{print $NF}' | sort | uniq -c | sort -nr | head

Привязка к процессу (дороже по времени и обычно требует прав):

ss -antp state close-wait
ss -antp state established | head

Если CLOSE_WAIT «висит» за конкретным PID/сервисом — это главный след. Если TIME_WAIT «размазан» по множеству исходящих портов и быстро обновляется — чаще это профиль трафика и частота создания соединений.

Если проблема всплыла на веб-прокси, полезно параллельно проверить таймауты на шлюзе: 502/504 нередко маскируют «залипание» апстрима или воркеров. По теме — диагностика gateway timeout между Nginx и PHP-FPM.

Сводка состояний TCP в выводе ss -s для быстрой диагностики

TIME_WAIT: что это и когда он реально мешает

TIME_WAIT — состояние стороны, которая активно закрыла TCP-соединение (обычно та, что отправила FIN первой). Соединение держится в TIME_WAIT, чтобы запоздалые пакеты старого соединения не «прилетели» в новое соединение с тем же 4-туплом (src/dst IP и порты), и чтобы корректно завершить обмен FIN/ACK при потерях.

TIME_WAIT длится порядка 2*MSL (в Linux обычно десятки секунд). Поэтому при высокой доле коротких соединений TIME_WAIT легко измеряется тысячами и десятками тысяч — и это может быть нормой.

Когда TIME_WAIT становится проблемой

  • Узел — «клиент» (прокси, микросервис, воркер), который делает много исходящих соединений к одному апстриму и упирается в диапазон ephemeral-портов. Симптомы: ошибки connect(), всплеск ретраев, рост латентности.
  • NAT/балансировщик активно закрывает соединения и на нём копится TIME_WAIT (вместо конечного сервера). Тогда лечить «сервер» бессмысленно.
  • Очень большой churn соединений плюс узкие места по CPU/irq/сети: TIME_WAIT виден как индикатор «много создаём/закрываем», но корень — в архитектуре или в настройках keep-alive.

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

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

CLOSE_WAIT: почти всегда утечка в приложении

CLOSE_WAIT означает: удалённая сторона уже прислала FIN и закрыла соединение, а ваш сокет ещё открыт. То есть приложение (или библиотека) не вызвало закрытие, либо «зависло» в обработке и не освобождает ресурсы.

Типовая картина:

  • клиент к апстриму/БД/HTTP API держит коннект и не закрывает его по таймауту;
  • воркер «утёк» (deadlock/ожидание) и держит сотни/тысячи сокетов;
  • connection pool настроен неверно (лимиты, keep-alive, idle timeout).

Если CLOSE_WAIT растёт и не уходит, вы почти гарантированно придёте к исчерпанию file descriptors.

Как быстро проверить file descriptors и связь с CLOSE_WAIT

Сначала — текущие лимиты и общий потолок:

ulimit -n
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr

Дальше — кто потребляет дескрипторы (быстрая грубая сортировка по PID):

ls /proc | grep -E '^[0-9]+$' | while read p; do echo -n "$p "; ls /proc/$p/fd 2>/dev/null | wc -l; done | sort -k2 -n | tail

И точечно по найденному PID:

PID=1234
ls -l /proc/$PID/fd | wc -l
ss -antp | grep -F "pid=$PID" | head

Повышение лимитов file descriptors — это «снятие симптома», а не лечение CLOSE_WAIT. Если утечка продолжается, вы просто дольше будете идти к падению.

Чтение ss: команды, которые чаще всего спасают в инциденте

Топ удалённых адресов (куда ходим или кто приходит)

ss -ant state time-wait | awk '{print $5}' | sort | uniq -c | sort -nr | head
ss -ant state established | awk '{print $5}' | sort | uniq -c | sort -nr | head

Листенеры и очередь accept (backlog)

ss -lnt
ss -lnt sport = :443

Смотрите значения очередей (send-q/recv-q). Если на листенере очередь стабильно большая, приложение не успевает принимать соединения или упирается в лимиты воркеров/backlog.

sysctl и лимиты: что помогает, а что трогать опасно

Ниже — параметры, которые обычно обсуждают рядом с TIME_WAIT/CLOSE_WAIT. Важно: sysctl не исправляет логические ошибки приложения, но может помочь ядру и сервисам переживать пики.

net.ipv4.tcp_fin_timeout: про FIN_WAIT, а не про TIME_WAIT

net.ipv4.tcp_fin_timeout влияет на длительность некоторых состояний завершения соединения (в частности, FIN_WAIT2). Это не прямой регулятор TIME_WAIT, но при накоплении «хвостов» закрытия может дать эффект.

sysctl net.ipv4.tcp_fin_timeout

Снижайте умеренно и только если вы видите именно накопление завершения, а не просто высокую скорость закрытий. Слишком агрессивные значения ухудшают поведение в проблемных сетях.

net.ipv4.tcp_tw_reuse: переиспользование TIME_WAIT для исходящих

net.ipv4.tcp_tw_reuse позволяет ядру переиспользовать сокеты в TIME_WAIT для новых исходящих соединений при определённых условиях. Это полезно на «клиентских» узлах, которые делают много исходящих коннектов к одним и тем же адресам.

sysctl net.ipv4.tcp_tw_reuse

Включайте только после измерений и понимания схемы трафика. Если у вас сложные middlebox/NAT, иногда это приводит к редким, но неприятным проблемам. И главное: это не «выключатель TIME_WAIT», а инструмент экономии локальных портов при исходящем churn.

net.core.somaxconn и backlog

net.core.somaxconn ограничивает максимальный accept backlog для слушающих сокетов. На пиках, когда приложение не успевает вызывать accept, это становится критичным.

sysctl net.core.somaxconn

Backlog — это связка из трёх уровней:

  • backlog, который выставляет приложение при listen();
  • ограничение ядра somaxconn;
  • очереди на стадии SYN (SYN backlog) и поведение при полуоткрытых соединениях.

Диапазон ephemeral-портов: часто безопаснее, чем «бороться с TIME_WAIT»

Если вы упираетесь в порты на исходящих соединениях, посмотрите диапазон локальных портов:

sysctl net.ipv4.ip_local_port_range

Расширение диапазона зачастую более предсказуемо, чем попытки «закрутить» TIME_WAIT. Но сначала убедитесь, что это именно исходящий трафик (ваш сервер как клиент), а не входящий (ваш сервер как сервис).

Лимиты file descriptors: системные и на сервис

При росте CLOSE_WAIT/ESTABLISHED держите под контролем:

  • лимит процесса (ulimit -n в контексте сервиса);
  • системный максимум (fs.file-max);
  • факт использования (/proc/sys/fs/file-nr).

Проверка лимитов для конкретного процесса (пример с Nginx):

cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep -i "open files"
Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Практический сценарий 1: много TIME_WAIT на веб-узле

  1. Определите, TIME_WAIT — входящие или исходящие. В ss -ant state time-wait смотрите локальный порт: если это 80/443, чаще всего это следствие входящего трафика и политики закрытия соединений.

  2. Проверьте keep-alive между клиентом, балансировщиком и вашим фронтендом. Отключённый keep-alive и слишком маленькие таймауты обычно создают «шторм коротких соединений».

  3. Если TIME_WAIT в основном на исходящих коннектах (фронтенд ходит в апстрим), оцените net.ipv4.ip_local_port_range, а net.ipv4.tcp_tw_reuse рассматривайте как дополнительную меру после замеров.

  4. Проверьте сопутствующие симптомы: ошибки connect/timeout, CPU, перегруз по accept/handshake.

Практический сценарий 2: растёт CLOSE_WAIT и падает сервис

  1. Зафиксируйте факт: ss -ant state close-wait | wc -l и затем ss -antp state close-wait, чтобы увидеть PID/имя процесса.

  2. Проверьте, это один сервис или несколько. Часто виноват конкретный воркер, который «залип» и держит основную массу сокетов.

  3. Смотрите логи и таймауты клиента (HTTP client, драйвер БД). CLOSE_WAIT появляется, когда удалённая сторона закрывает соединение по своему таймауту, а ваш код не закрывает сокет у себя.

  4. Тактика на инцидент: временно поднять лимиты FD, чтобы выиграть время; локализовать воркер; перезапустить; затем исправить первопричину в коде/конфигурации.

Проверка потребления file descriptors процессами при росте CLOSE_WAIT

Минимальные «безопасные» действия, которые почти всегда окупаются

  • Настройте мониторинг по состояниям TCP (как минимум значения из ss -s) и алерты на рост CLOSE_WAIT.
  • Приведите в порядок лимиты file descriptors для сервисов и системный потолок.
  • Согласуйте backlog приложения и net.core.somaxconn, проверьте, что очередь не искусственно занижена.
  • Для узлов с большим числом исходящих соединений: расширьте net.ipv4.ip_local_port_range и уменьшайте churn за счёт keep-alive и пулов.

Пример: аккуратное применение sysctl (шаблон)

Изменения лучше оформлять отдельным файлом в /etc/sysctl.d/, чтобы было легко откатить:

sudo nano /etc/sysctl.d/99-tcp-tuning.conf

Пример содержимого (это не универсальные значения, а демонстрация подхода):

net.core.somaxconn = 4096
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

Применение и проверка:

sudo sysctl --system
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse net.ipv4.tcp_fin_timeout

Перед изменениями зафиксируйте baseline: сколько TIME_WAIT/CLOSE_WAIT, какие ошибки в приложении, какая нагрузка. Иначе вы не поймёте, помогло ли, или просто «повезло» со спадом трафика.

Частые вопросы и ошибки

«У меня много TIME_WAIT — надо срочно уменьшить таймаут»

TIME_WAIT — нормальная часть TCP. Если нет упора в порты/лимиты и нет симптомов, «лечить цифру» не нужно. Обычно выгоднее включить keep-alive и использовать пулы соединений, чем пытаться убрать TIME_WAIT любой ценой.

«У меня много CLOSE_WAIT — подкручу sysctl и пройдёт»

Не пройдёт. CLOSE_WAIT — это поведение процесса. sysctl не заставит приложение закрыть сокет. Ваш путь: PID, код/библиотека, таймауты, лимиты пула, иногда обновление рантайма/драйвера.

«Поднимем лимит file descriptors — и всё»

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

Итог

TIME_WAIT чаще про профиль трафика и частоту создания соединений, а CLOSE_WAIT — про дисциплину закрытия сокетов вашим приложением. Начинайте с диагностики через ss -s, затем проваливайтесь в распределение по портам/адресам и привязку к процессам. И только после этого трогайте sysctl: net.core.somaxconn — про backlog, net.ipv4.tcp_fin_timeout — про «хвосты» закрытия, net.ipv4.tcp_tw_reuse — про ускорение повторного использования для исходящих соединений. А лимиты file descriptors держите под контролем всегда — они первыми заканчиваются при CLOSE_WAIT.

Если вы разворачиваете сервисы на отдельном сервере и хотите больше контроля над сетью и лимитами, удобнее работать на VDS, где вы сами задаёте sysctl и системные лимиты под нагрузку. Для проектов попроще часто достаточно виртуального хостинга — там меньше свободы тюнинга, но и меньше забот по администрированию.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...