Address already in use: как в Linux найти, кто занял порт, и освободить его
Команда KAI Lab · · 8 мин чтения
Ошибка Address already in use (в Linux это EADDRINUSE, код 98) значит, что программа пытается слушать адрес и порт, которые уже заняты другим сокетом. Найдите владельца командой sudo ss -ltnp 'sport = :8080' или sudo lsof -nP -iTCP:8080 -sTCP:LISTEN, а дальше решайте: остановить старый процесс, сменить порт или исправить конфигурацию — убивать найденный процесс вслепую не нужно.
Так эта ошибка выглядит в разных программах:
# nginx
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
# Python
OSError: [Errno 98] Address already in use
# Node.js
Error: listen EADDRINUSE: address already in use :::3000
# Docker при публикации порта
Bind for 0.0.0.0:8080 failed: port is already allocatedСимптом → команда → что смотреть
| Симптом | Команда | Что смотреть |
|---|---|---|
Address already in use при старте | sudo ss -ltnp 'sport = :8080' | процесс в столбце Process: users:(("имя",pid=...)) |
| Нужна вторая проверка | sudo lsof -nP -iTCP:8080 -sTCP:LISTEN | COMMAND, PID, USER |
| Непонятно, откуда процесс | systemctl status PID | к какому сервису или SSH-сессии он относится |
Порт держит systemd с pid=1 | systemctl list-sockets | какой .socket-юнит слушает порт |
Порт держит docker-proxy | docker ps | контейнер, который опубликовал этот порт |
| Ошибка сразу после перезапуска своего сервера | ss -tno state time-wait | соединения в TIME-WAIT на этом порту |
Permission denied на порту ниже 1024 | sysctl net.ipv4.ip_unprivileged_port_start | нужен root или CAP_NET_BIND_SERVICE |
Cannot assign requested address | ip -br addr | есть ли на хосте IP-адрес из конфига |
Шаг 1. Кто слушает порт: ss
$ sudo ss -ltnp 'sport = :8080'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 100 *:8080 *:* users:(("java",pid=41822,fd=112))Флаги: -l — только слушающие сокеты, -t — TCP, -n — не заменять номера портов именами служб, -p — показать процесс. Для UDP замените -t на -u. Без sudo столбец Process для чужих процессов останется пустым — частая причина недоумения «порт занят, а процесса нет».
Адрес *:8080 означает, что сокет слушает все адреса сразу, IPv4 и IPv6. Об этом ниже: такой сокет не даст другому процессу занять тот же порт даже на одном конкретном адресе.
Шаг 2. Проверить вторым способом: lsof и fuser
$ sudo lsof -nP -iTCP:8080 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 41822 app 112u IPv6 18839021 0t0 TCP *:8080 (LISTEN)
$ sudo fuser -v 8080/tcp
USER PID ACCESS COMMAND
8080/tcp: app 41822 F.... javaУ lsof флаги -n и -P отключают перевод адресов и портов в имена — так быстрее и нагляднее. fuser удобен в скриптах: без -v он печатает только PID. Осторожно с fuser -k: по умолчанию он посылает SIGKILL, и процесс не успевает ни закрыть соединения, ни дописать данные. Если убивать, то хотя бы с -TERM.
Шаг 3. Что это за процесс и кто его запустил
Прежде чем что-то останавливать, выясните, что это. Самый быстрый способ — спросить systemd, к какому юниту относится PID:
$ ps -o pid,user,lstart,cmd -p 41822
PID USER STARTED CMD
41822 app Wed Sep 23 18:40:02 2026 java -jar /opt/orders/orders-api.jar
$ systemctl status 41822
● session-12.scope - Session 12 of User alice
Loaded: loaded (/run/systemd/transient/session-12.scope; transient)
Transient: yes
Active: active (running) since Wed 2026-09-23 18:40:02 UTC; 8h agoКартина понятна: вчера вечером коллега запустил копию сервиса руками в своей SSH-сессии — например, чтобы проверить новую сборку, — и забыл её остановить. Теперь orders-api.service не может занять порт. Правильный порядок: договориться с коллегой, остановить процесс мягко (kill 41822 посылает SIGTERM), убедиться через ss, что порт свободен, и запустить сервис через systemd.
Кто обычно занимает порт
Старая копия того же приложения. Запущена руками через nohup, tmux или screen, не остановлена скриптом деплоя или одновременно работает и как systemd-сервис, и в контейнере.
Другой сервис на том же порту. Классика — Apache и nginx, которые оба хотят порт 80: пакет поставили для проверки, и он запустился по умолчанию. Если сервис не нужен, sudo systemctl disable --now apache2.
docker-proxy. Если в Process стоит docker-proxy, порт опубликовал контейнер (-p 8080:80 или ports: в compose). Найдите его в docker ps по столбцу PORTS. Сам Docker в такой ситуации пишет port is already allocated.
systemd со своим сокетом. Если порт держит systemd с pid=1, работает socket activation: systemd сам слушает порт и запускает сервис при первом подключении. Какой юнит слушает, покажет systemctl list-sockets. Остановить только сервис мало — порт держит .socket-юнит. Пример из жизни: в Ubuntu начиная с 22.10 OpenSSH по умолчанию запускается через socket activation, поэтому владельцем порта 22 в ss будет systemd.
IPv6-сокет, который принимает и IPv4. В Linux по умолчанию net.ipv6.bindv6only = 0: сокет на [::]:8080 принимает и IPv4-подключения. Поэтому если одна программа слушает [::]:8080, другая не сможет занять 0.0.0.0:8080. Решение — один сокет на оба протокола или опция IPV6_V6ONLY на IPv6-сокете. В nginx она включена по умолчанию (ipv6only=on), поэтому listen 80; и listen [::]:80; в одном конфиге уживаются.
Сокет на всех адресах. Из man 7 socket: если слушающий сокет привязан ко всем адресам (0.0.0.0) с конкретным портом, этот порт нельзя занять ни на одном локальном адресе. Процесс на 0.0.0.0:8080 не даст другому слушать 127.0.0.1:8080.
TIME_WAIT: порт «занят» сразу после перезапуска
Бывает, что процесса на порту нет, а свой сервер после перезапуска минуту не может подняться. Причина — соединения в состоянии TIME-WAIT. Сторона, которая первой закрыла TCP-соединение, держит его ещё какое-то время, чтобы поймать опоздавшие пакеты. В Linux это время зашито в ядро: TCP_TIMEWAIT_LEN, 60 секунд.
$ ss -tno state time-wait '( sport = :8080 )'
Recv-Q Send-Q Local Address:Port Peer Address:Port Process
0 0 10.0.0.12:8080 10.0.0.31:51724 timer:(timewait,42sec,0)
0 0 10.0.0.12:8080 10.0.0.31:51730 timer:(timewait,43sec,0)Флаг -o показывает таймеры: этим соединениям осталось 42–43 секунды. Если программа не выставляет перед bind() опцию SO_REUSEADDR, такие соединения мешают ей занять порт. С SO_REUSEADDR привязка разрешена всегда, кроме случая, когда на адресе уже есть активный слушающий сокет, — так описано в man 7 socket. Большинство серверов ставят эту опцию сами, а в своём коде её легко забыть:
import socket
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # до bind()
srv.bind(("0.0.0.0", 8080))
srv.listen()Не путайте с SO_REUSEPORT. Эта опция (Linux 3.9 и новее) позволяет нескольким сокетам слушать один и тот же порт одновременно, чтобы ядро распределяло между ними подключения. Выставить её должны все сокеты, и процессы должны работать от одного пользователя. Чужой процесс на вашем порту она не вытеснит.
Настройки ядра здесь обычно ни при чём. tcp_tw_reuse касается исходящих соединений, и документация ядра прямо просит не менять его без совета специалистов. tcp_fin_timeout управляет состоянием FIN_WAIT_2, а не TIME-WAIT. А tcp_tw_recycle из старых советов в документации современных ядер уже нет.
Не «занят», а «нельзя»: две похожие ошибки
Permission denied на порту ниже 1024. Это не конфликт, а права: порты ниже net.ipv4.ip_unprivileged_port_start (по умолчанию 1024) может занимать только процесс с root или с возможностью CAP_NET_BIND_SERVICE. Для systemd-сервиса, который работает не от root, это решается строкой AmbientCapabilities=CAP_NET_BIND_SERVICE в секции [Service]. Проще и привычнее — слушать высокий порт за reverse proxy.
Cannot assign requested address. Программа пытается слушать IP-адрес, которого на хосте нет: после переезда в конфиге остался старый адрес или сервис стартует раньше, чем интерфейс получил IP. Проверьте ip -br addr.
Как освободить порт правильно
- Найти владельца:
sudo ss -ltnp 'sport = :порт'. - Понять, что это за процесс:
systemctl status PID,ps -o pid,user,lstart,cmd -p PID. - Если это сервис —
sudo systemctl stop имя, а если он вообще не нужен на этой машине, ещё иdisable. - Если это зависший процесс —
kill PID, подождать, и только если он не завершается,kill -9 PID. - Запустить свой сервис и проверить:
ss -ltnpпоказывает его, аcurlполучает ответ.
Если сервис после этого всё равно не стартует, разбирайтесь по его журналу — порядок в статье сервис systemd не запускается. А если порт свободен и сервис слушает, но клиенты не могут подключиться по имени, следующая остановка — DNS не резолвит.
В симуляторе DevOps KAI Lab сетевые задачи начинаются с первого месяца: консоль отвечает реальными выводами ss, curl и логов nginx, и разбираться приходится на «живом» сервере вымышленной компании, а не по картинкам.
Частые вопросы
Как узнать, какой процесс занимает порт в Linux? sudo ss -ltnp 'sport = :8080' — процесс будет в столбце Process. Альтернатива — sudo lsof -nP -iTCP:8080 -sTCP:LISTEN или sudo fuser -v 8080/tcp. Без sudo чужие процессы не видны.
Сколько длится TIME_WAIT в Linux? 60 секунд: константа TCP_TIMEWAIT_LEN зашита в ядро. Параметр tcp_fin_timeout, который часто советуют уменьшить, относится к состоянию FIN_WAIT_2, а не к TIME-WAIT.
Чем SO_REUSEADDR отличается от SO_REUSEPORT? SO_REUSEADDR разрешает занять порт, пока на нём висят соединения в TIME-WAIT, но не при живом слушающем сокете. SO_REUSEPORT позволяет нескольким сокетам одного пользователя слушать один порт одновременно и делить подключения.
Можно ли просто сделать kill -9? Можно, но это последний шаг. SIGKILL не даёт процессу закрыть соединения и дописать данные. Сначала kill PID — это SIGTERM, — и только если процесс не завершается, kill -9.
Почему после остановки сервиса порт всё равно занят? Либо осталась копия, запущенная вне systemd, либо порт держит .socket-юнит, либо контейнер через docker-proxy. Посмотрите, кто владелец, в sudo ss -ltnp.
Источники
- man7.org: ss(8) — флаги
-l,-t,-n,-p,-o, фильтры по состоянию. - man7.org: lsof(8), fuser(1) —
-iTCP -sTCP:LISTEN,-kпосылаетSIGKILLпо умолчанию. - man7.org: socket(7) —
SO_REUSEADDR,SO_REUSEPORT. - man7.org: ip(7) —
EADDRINUSE, привилегированные порты иCAP_NET_BIND_SERVICE. - Linux kernel. IP Sysctl —
tcp_tw_reuse,tcp_fin_timeout,ip_unprivileged_port_start,bindv6only. - Linux kernel. include/net/tcp.h —
TCP_TIMEWAIT_LEN(60 секунд). - Moby (Docker). portallocator.go — текст ошибки
port is already allocated. - Ubuntu Community Hub. SSHd now uses socket-based activation (Ubuntu 22.10 and later).
- nginx. Директива listen — параметр
ipv6only, включён по умолчанию.