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.

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 и пулы соединений, чем твики ядра.
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"
Практический сценарий 1: много TIME_WAIT на веб-узле
Определите, TIME_WAIT — входящие или исходящие. В
ss -ant state time-waitсмотрите локальный порт: если это 80/443, чаще всего это следствие входящего трафика и политики закрытия соединений.Проверьте keep-alive между клиентом, балансировщиком и вашим фронтендом. Отключённый keep-alive и слишком маленькие таймауты обычно создают «шторм коротких соединений».
Если TIME_WAIT в основном на исходящих коннектах (фронтенд ходит в апстрим), оцените
net.ipv4.ip_local_port_range, аnet.ipv4.tcp_tw_reuseрассматривайте как дополнительную меру после замеров.Проверьте сопутствующие симптомы: ошибки connect/timeout, CPU, перегруз по accept/handshake.
Практический сценарий 2: растёт CLOSE_WAIT и падает сервис
Зафиксируйте факт:
ss -ant state close-wait | wc -lи затемss -antp state close-wait, чтобы увидеть PID/имя процесса.Проверьте, это один сервис или несколько. Часто виноват конкретный воркер, который «залип» и держит основную массу сокетов.
Смотрите логи и таймауты клиента (HTTP client, драйвер БД). CLOSE_WAIT появляется, когда удалённая сторона закрывает соединение по своему таймауту, а ваш код не закрывает сокет у себя.
Тактика на инцидент: временно поднять лимиты FD, чтобы выиграть время; локализовать воркер; перезапустить; затем исправить первопричину в коде/конфигурации.

Минимальные «безопасные» действия, которые почти всегда окупаются
- Настройте мониторинг по состояниям 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 и системные лимиты под нагрузку. Для проектов попроще часто достаточно виртуального хостинга — там меньше свободы тюнинга, но и меньше забот по администрированию.


