Вчера снова столкнулся с этой ошибкой. Запускаю локальный сервер, а он мне в ответ: "Error: listen EADDRINUSE: address already in use :::3000". Знакомо до боли, правда? Эта проблема преследует разработчиков как тень, и каждый раз приходится вспоминать нужные команды. Сегодня расскажу все способы решения, которые накопил за годы работы с Linux, Windows и macOS.

Анатомия ошибки: почему порты конфликтуют

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

Помню, как коллега час искал причину, почему Node.js не запускается на порту 5000. Оказалось, в macOS Monterey этот порт по умолчанию занимает AirPlay Receiver! Такие сюрпризы случаются после обновления системы. Иногда виновниками становятся Skype (любит порт 80), Apache, nginx или даже антивирусы.

Интересная деталь: если закрыть терминал комбинацией Ctrl+Z вместо Ctrl+C, процесс останется висеть в фоне. Первая команда только приостанавливает выполнение, вторая действительно завершает. Многие попадаются на этом, особенно при работе с nodemon или pm2.

Современный подход через ss в Linux

Утилита ss пришла на смену устаревшему netstat и работает значительно быстрее. Она напрямую обращается к ядру Linux через Netlink API, минуя промежуточные слои. Вот мой любимый способ найти захватчика порта:

sudo ss -tulnp 'sport = :3000'

Расшифровка флагов: t показывает TCP-соединения, u добавляет UDP, l фильтрует только прослушивающие сокеты, n выводит числовые значения портов, p раскрывает информацию о процессах. Вывод покажет что-то вроде:

LISTEN 0 128 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=241907,fd=12))

Видите PID 241907? Это наш целевой процесс. Кстати, если нужно проверить IPv6, добавьте фильтр для ":::3000". Система может слушать на обоих протоколах одновременно, что иногда создает путаницу.

Альтернативный синтаксис для поиска:

sudo ss -lntup | grep :3000

Классический netstat: проверенный временем

Несмотря на появление ss, netstat остается рабочей лошадкой во многих системах. В некоторых дистрибутивах его нужно установить отдельно через пакет net-tools:

sudo netstat -tulpan | grep :3000

Флаги похожи на ss, но есть нюанс: флаг -a показывает все соединения, включая TIME_WAIT и CLOSE_WAIT. Эти состояния часто путают новичков - они не означают, что порт занят для прослушивания. Ищите именно LISTEN!

Вывод netstat более компактный:

tcp 0 0 0.0.0.0:3000 0.0.0.0:* LISTEN 241907/node

На старых системах без поддержки -p придется использовать комбинацию с ps:

sudo netstat -tln | grep :3000
ps aux | grep node

Швейцарский нож lsof для детального анализа

Утилита lsof (list open files) показывает все открытые файлы и сетевые соединения. Её преимущество - человекочитаемый вывод с подробной информацией:

sudo lsof -i :3000

Результат выглядит информативно:

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 241907 user 19u IPv4 123456 0t0 TCP *:3000 (LISTEN)

Хитрость: флаг -t выводит только PID, что удобно для скриптов:

sudo kill -9 $(sudo lsof -t -i:3000)

Эта однострочная команда находит и сразу убивает процесс. Использую её так часто, что создал алиас в .bashrc.

Быстрое решение через fuser

Утилита fuser специально создана для работы с процессами, использующими файлы или порты:

sudo fuser -v -n tcp 3000

Флаг -v добавляет подробности, -n указывает пространство имен (tcp или udp). Но главная фишка fuser - возможность мгновенно освободить порт:

sudo fuser -k 3000/tcp

Команда отправляет SIGKILL всем процессам на указанном порту. Будьте осторожны - это жёсткое завершение без сохранения данных!

Windows: netstat и taskkill в действии

В Windows подход отличается, но логика та же. Открываю PowerShell или cmd от имени администратора:

netstat -ano | findstr :3000

Флаг -o критически важен - он показывает PID. Вывод:

TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 27924

Последнее число (27924) - наш PID. Проверяю, что за процесс:

tasklist /FI "PID eq 27924"

Для завершения:

taskkill /PID 27924 /F

Флаг /F означает принудительное завершение. Без него Windows попытается закрыть процесс корректно, что не всегда срабатывает.

В PowerShell доступны более мощные командлеты:

Get-NetTCPConnection -LocalPort 3000 | Select-Object -Property OwningProcess
Stop-Process -Id 27924 -Force

Альтернативный вариант с netstat -b (требует админские права):

netstat -bona | findstr :3000

Флаг -b показывает имя исполняемого файла, но замедляет выполнение команды.

macOS: особенности и подводные камни

На Mac команды похожи на Linux, но есть нюансы. Основной инструмент - lsof:

sudo lsof -i :3000
kill -9 <PID>

Удобный однострочник:

kill -9 $(lsof -ti:3000)

Или через xargs:

lsof -ti:3000 | xargs kill

Специфика macOS: системные службы любят занимать популярные порты. AirPlay Receiver сидит на 5000, а различные процессы Apple могут занять 7000 или 8080. Проверяю через:

sudo lsof -iTCP -sTCP:LISTEN

Для Node.js разработчиков есть npm-пакет kill-port:

npx kill-port 3000

Правильное завершение: от мягкого к жёсткому

Нашли PID? Теперь важно правильно завершить процесс. Всегда начинаю с мягкого подхода:

kill <PID>        # отправляет SIGTERM (15)
kill -15 <PID>    # то же самое, явно указан сигнал

SIGTERM даёт процессу шанс корректно завершиться, сохранить данные, закрыть соединения. Жду 5-10 секунд. Если процесс упрямится:

kill -9 <PID>     # SIGKILL - немедленное завершение

SIGKILL работает как аварийная кнопка - процесс умирает мгновенно, но может оставить мусор в памяти или незакрытые файлы.

Системные службы и Docker: особые случаи

Если порт занят системной службой, лучше использовать менеджер служб. Для systemd в Linux:

sudo systemctl stop nginx
sudo systemctl status nginx

Для Docker-контейнеров сначала проверяю маппинг портов:

docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}"

Останавливаю контейнер:

docker stop <container-id>

В WSL (Windows Subsystem for Linux) порты могут быть заняты как Linux-процессами, так и Windows-приложениями. Проверяю с обеих сторон!

Автоматизация и практические трюки

За годы работы накопил коллекцию полезных скриптов. Вот функция для .bashrc/.zshrc:

killport() {
    if [ -z "$1" ]; then
        echo "Usage: killport <port>"
        return 1
    fi
    
    local pid=$(lsof -t -i:$1)
    if [ -z "$pid" ]; then
        echo "No process found on port $1"
        return 1
    fi
    
    echo "Killing process $pid on port $1"
    kill -9 $pid
}

Теперь достаточно написать killport 3000.

Для разработки использую nodemon с правильными настройками в package.json:

{
  "scripts": {
    "dev": "nodemon --signal SIGTERM --exec node server.js"
  }
}

PM2 тоже отлично справляется с управлением процессами:

pm2 start app.js
pm2 stop all
pm2 kill

Диагностика сложных случаев

Иногда процесс не находится стандартными методами. Проверяю состояния соединений:

ss -tan | grep TIME_WAIT

TIME_WAIT - это нормальное состояние после закрытия соединения. Порт освободится через минуту-две. CLOSE_WAIT указывает на проблему в приложении - оно не закрывает соединения правильно.

Если порт занят, но процесс не виден, возможно, он запущен от другого пользователя:

sudo ss -tulnp | grep :3000

На виртуальных машинах порт может быть проброшен с хоста. Проверяю настройки VirtualBox или VMware.

Профилактика: как избежать проблем

Предотвратить легче, чем лечить. Мои правила:

Всегда использую переменные окружения для портов:

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

Добавляю обработчики сигналов в Node.js:

process.on('SIGTERM', () => {
    server.close(() => {
        console.log('Server closed');
    });
});

Настраиваю graceful shutdown в Docker:

STOPSIGNAL SIGTERM

Использую разные порты для разных проектов. Создал себе схему: 3000-3099 для Node.js, 8000-8099 для Python, 4200 для Angular. Это минимизирует конфликты.

Регулярно проверяю открытые порты командой ss -tuln или netstat -tuln. Если вижу неизвестные процессы, исследую через lsof или ps aux.

Помните: каждая ошибка EADDRINUSE - это возможность лучше узнать свою систему. После десятка решённых конфликтов портов вы будете ориентироваться в процессах как рыба в воде. Главное - не паниковать и методично применять правильные инструменты.