netstat и ss: какие порты слушает сервер и что означают состояния TCP
Снаружи порт закрыт, изнутри сервис вроде запущен. Или наоборот: сайт отвечает, но раз в час намертво зависает на минуту. Обе ситуации разбираются одной командой — если знать, куда смотреть. Разбираем ss и netstat: ключи, фильтры, одиннадцать состояний TCP и две колонки, которые видит только ss.
ss и netstat — утилиты, которые показывают сокеты операционной системы: какие порты она слушает, с кем установлены соединения и в каком те состоянии. Это взгляд изнутри сервера, в отличие от сканирования снаружи: ss видит и процесс-владелец сокета, и очереди, которых снаружи не видно вовсе.
Почему netstat может не найтись на сервере
netstat входит в пакет net-tools, последний релиз которого — 2.10 от января 2021 года. За пять с лишним лет ни одного обновления: пакет живёт только на патчах дистрибутивов. На смену пришёл iproute2, который активно развивается.
| Старая команда | Замена | Что показывает |
|---|---|---|
| netstat | ss | Сокеты |
| netstat -r | ip route | Таблица маршрутов |
| netstat -i | ip -s link | Статистика по интерфейсам |
| netstat -g | ip maddr | Multicast-группы |
| netstat -s | nstat | Счётчики протоколов, точнее |
| ifconfig | ip addr | Адреса интерфейсов |
| route | ip route | Маршруты |
| arp | ip neigh | ARP-таблица |
Где netstat уже не стоит по умолчанию: RHEL начиная с седьмой версии, Debian и Ubuntu (пакет с приоритетом optional против important у iproute2), Fedora, Arch, Alpine и все контейнерные образы. В докер-контейнере вы почти гарантированно не найдёте ни того, ни другого — ставить придётся руками.
Главная команда, которую нужно помнить
sudo ss -tulpn # что слушает сервер: TCP, UDP, процессы, без резолва sudo netstat -tulpn # то же на старых системах
Мнемоника одинаковая: tcp, udp, listening, process, numeric. Без sudo колонка с процессами будет пустой для чужих сокетов — ядро не покажет владельца непривилегированному пользователю.
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=812,fd=6))
tcp LISTEN 0 511 127.0.0.1:9000 0.0.0.0:* users:(("php-fpm",pid=901,fd=8))
tcp LISTEN 0 70 127.0.0.1:33060 0.0.0.0:* users:(("mysqld",pid=744,fd=21))
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=611,fd=13))
0.0.0.0:80 — сервис слушает на всех интерфейсах, доступен снаружи. 127.0.0.1:9000 — только с самой машины: снаружи такой порт будет выглядеть закрытым, и это чаще всего правильно. Самая частая причина «порт закрыт, хотя сервис запущен» — именно привязка к localhost вместо внешнего адреса.
Ключи ss
| Ключ | Что делает |
|---|---|
| -t / -u | TCP / UDP |
| -l | Только слушающие. Без него они скрыты |
| -a | Все: и слушающие, и установленные |
| -n | Не резолвить имена — быстрее и понятнее |
| -p | Показать процессы-владельцы |
| -i | Внутренности TCP: rtt, cwnd, retrans, алгоритм контроля перегрузки |
| -m | Память сокета — ищем утечки буферов |
| -e | Расширенно: uid, inode, cookie |
| -o | Таймеры: keepalive, timewait, persist |
| -s | Сводка по счётчикам, без перечисления сокетов |
| -4 / -6 | Только IPv4 / только IPv6 |
| -x | Unix-сокеты |
| -H | Без строки заголовка. Обязательно для скриптов |
| -K | Принудительно закрыть подходящие под фильтр сокеты |
| -N имя | Заглянуть в сетевой namespace контейнера |
Фильтры ss: главное преимущество перед netstat
У ss есть собственный язык выражений, и фильтрация происходит в ядре, а не после выгрузки всего списка. На машине с сотней тысяч соединений это разница между долями секунды и десятками секунд.
# По состоянию
ss -t state established
ss -t state time-wait
ss -t state close-wait # диагностика утечки дескрипторов
ss -t state syn-sent # висящие исходящие соединения
# По адресу и подсети
ss -t dst 10.0.0.5
ss -t dst 10.0.0.0/8
ss -t dst 10.0.0.5:443
ss -t src 192.168.1.10
# По портам
ss -t sport = :22
ss -tn 'sport > :1024'
ss -t '( dport = :443 or sport = :443 )'
# Комбинация
ss -tn state established '( dport = :80 or dport = :443 )' dst 10.0.0.0/8
# Топ IP-адресов, с которых идут соединения
ss -tn state established -H | awk '{print $5}' | cut -d: -f1 \
| sort | uniq -c | sort -rn | head -20
# Сводка по состояниям одной командой
ss -tan | awk 'NR>1{c[$1]++} END{for (s in c) print c[s], s}' | sort -rn
# Аварийно оборвать соединения с конкретным адресом
sudo ss -K dst 10.0.0.5 dport = :443
Почему ss быстрее: netstat читает и разбирает текстовые файлы /proc/net/tcp, которые ядро генерирует построчно, а с ключом -p ещё и обходит все дескрипторы всех процессов, чтобы сопоставить inode сокета с владельцем. ss использует netlink-интерфейс sock_diag: двоичный формат, запрос только нужных полей и фильтрация на стороне ядра.
Одиннадцать состояний TCP и что означает их скопление
| Состояние | Смысл | Если их много |
|---|---|---|
| LISTEN | Сокет ждёт входящих | Норма. Тревожно, когда ожидаемого LISTEN нет |
| SYN-SENT | Мы послали SYN, ответа нет | Исходящие не устанавливаются: файрвол дропает, внешняя зависимость лежит |
| SYN-RECV | Пришёл SYN, финального ACK нет | SYN-флуд или асимметричная маршрутизация |
| ESTAB | Рабочее соединение | Норма. Много без трафика — нет таймаутов или утечка |
| FIN-WAIT-1 | Мы послали FIN, ACK не пришёл | Пир молчит или связь оборвалась |
| FIN-WAIT-2 | Ждём FIN от пира | Пир не закрывает свою сторону. Таймер 60 секунд |
| CLOSE-WAIT | Пир закрылся, наше приложение — нет | Всегда баг приложения. Таймаута нет |
| LAST-ACK | Ждём финальный ACK | Обычно единицы. Накопление — пир исчез |
| CLOSING | Одновременное закрытие с обеих сторон | Редкость, массово не встречается |
| TIME-WAIT | Закрывший активно держит четвёрку 60 секунд | Обычно норма, а не проблема |
| CLOSED | Сокета нет | В выводе практически не появляется |
У состояния CLOSE-WAIT нет таймаута: оно живёт, пока процесс не закроет дескриптор или не умрёт. Причины всегда одинаковые — незакрытые соединения в пуле, забытый close, необработанное исключение, зависший воркер. Финал предсказуем: «too many open files» и отказ сервиса. Никакой sysctl это не чинит. Диагностика: ss -tanp state close-wait, затем ls /proc/PID/fd | wc -l против cat /proc/PID/limits.
Про TIME-WAIT отдельно, потому что вокруг него много устаревших советов. В Linux длительность жёстко зашита в ядро — 60 секунд, и sysctl её не меняет. Десятки тысяч таких сокетов на busy-прокси — ожидаемая картина, а не авария. Реальная проблема возникает только при исчерпании эфемерных портов к одному и тому же адресу с портом; симптом — ошибка EADDRNOTAVAIL. Лечится это пулом соединений и keep-alive, расширением net.ipv4.ip_local_port_range и параметром tcp_tw_reuse=1. А вот tcp_tw_recycle, который до сих пор советуют в старых статьях, удалён из ядра ещё в версии 4.12 — он ломал клиентов за NAT.
Recv-Q и Send-Q: две колонки, которые читают неправильно
У этих колонок разный смысл для слушающего и для установленного сокета. Путаница здесь стоит часов диагностики.
| Состояние | Recv-Q | Send-Q |
|---|---|---|
| LISTEN | Сколько установленных соединений ждут accept | Максимальный размер этой очереди |
| ESTABLISHED | Байты приняты ядром, но не прочитаны приложением | Байты отданы в сокет, но не подтверждены пиром |
ss -ltn State Recv-Q Send-Q Local Address:Port LISTEN 0 4096 0.0.0.0:80 <- очередь пуста, всё хорошо LISTEN 129 128 0.0.0.0:8080 <- очередь переполнена, соединения теряются
| Что видим | Диагноз | Что делать |
|---|---|---|
| LISTEN, Recv-Q ползёт к Send-Q | Приложение не успевает принимать соединения | Больше воркеров, поднять backlog и somaxconn |
| LISTEN, Send-Q = 128 при somaxconn 4096 | Приложение само попросило маленькую очередь | Править конфиг сервиса, sysctl не поможет |
| ESTAB, большой Recv-Q | Приложение не читает из сокета | Искать залипший поток или медленного потребителя |
| ESTAB, большой Send-Q | Сеть или пир не принимают | Смотреть ss -tin: retrans, cwnd, rtt |
netstat для слушающего сокета печатает в Send-Q бессмысленный ноль и никогда не показывает максимальный размер очереди. Проверить, не теряются ли соединения на входе, можно только через ss -ltn. Подтвердить догадку помогут счётчики: nstat -az | grep -E 'ListenOverflows|ListenDrops'.
Разбор трёх типовых ситуаций
Порт снаружи закрыт, сервис запущен. Смотрим адрес привязки: если 127.0.0.1:8080, сервис принципиально недоступен снаружи, и дело не в файрволе. Если 0.0.0.0:8080 — тогда уже файрвол или группа безопасности у хостера.
Сайт зависает под нагрузкой. Смотрим ss -ltn: если Recv-Q растёт — приложению не хватает воркеров, соединения ждут в очереди и отваливаются по таймауту у клиента, при этом в логах веб-сервера может не быть вообще ничего.
Сервис умирает раз в неделю. Смотрим ss -tan state close-wait | wc -l. Если счёт идёт на сотни и растёт день ото дня — утечка дескрипторов, и падение случится, когда упрётся в лимит.
Что делать на Windows и macOS
Утилиты ss там нет и не будет: она опирается на netlink, которого нет за пределами Linux.
# Windows
netstat -ano # все сокеты с PID
netstat -ano | findstr :443
tasklist /FI "PID eq 1234" # сопоставить PID с процессом
# PowerShell, объектный вариант
Get-NetTCPConnection -State Listen |
Select LocalAddress,LocalPort,OwningProcess,
@{n='Process';e={(Get-Process -Id $_.OwningProcess).ProcessName}}
# macOS: netstat есть, но BSD-версии, PID не показывает
sudo lsof -i -P -n # реальный аналог ss -tulpn
sudo lsof -i :443
netstat -an # -p здесь означает протокол, а не процесс
Взгляд изнутри не заменяет взгляда снаружи
ss показывает, что сокет открыт и слушает. Он не показывает, доходит ли до него трафик из интернета: между вашим сервисом и пользователем могут стоять файрвол, группа безопасности хостера, NAT и провайдерская фильтрация. Порт может слушать идеально и быть недоступен всей стране.
Проверить это можно только снаружи, причём из нескольких мест: проверка TCP-порта устанавливает соединение с указанным портом с внешних точек и показывает, где оно проходит, а где нет. А ICMP-проверка отвечает на предыдущий вопрос — виден ли вообще сам хост.
Пруфен проверяет доступность портов и хостов с интервалом от 2 минут из 13 точек мониторинга и присылает уведомление в Email, Telegram или Макс, когда порт перестал принимать соединения; если он не поднимается, подключается голосовой звонок. Результат виден по каждой локации отдельно — сразу понятно, это сервер лёг или закрылся маршрут из одного региона.