kai.kzLab

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:LISTENCOMMAND, PID, USER
Непонятно, откуда процессsystemctl status PIDк какому сервису или SSH-сессии он относится
Порт держит systemd с pid=1systemctl list-socketsкакой .socket-юнит слушает порт
Порт держит docker-proxydocker psконтейнер, который опубликовал этот порт
Ошибка сразу после перезапуска своего сервераss -tno state time-waitсоединения в TIME-WAIT на этом порту
Permission denied на порту ниже 1024sysctl net.ipv4.ip_unprivileged_port_startнужен root или CAP_NET_BIND_SERVICE
Cannot assign requested addressip -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.

Как освободить порт правильно

  1. Найти владельца: sudo ss -ltnp 'sport = :порт'.
  2. Понять, что это за процесс: systemctl status PID, ps -o pid,user,lstart,cmd -p PID.
  3. Если это сервис — sudo systemctl stop имя, а если он вообще не нужен на этой машине, ещё и disable.
  4. Если это зависший процесс — kill PID, подождать, и только если он не завершается, kill -9 PID.
  5. Запустить свой сервис и проверить: 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.

Источники

Читать дальше