Вчера снова столкнулся с этой ошибкой. Запускаю локальный сервер, а он мне в ответ: "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 - это возможность лучше узнать свою систему. После десятка решённых конфликтов портов вы будете ориентироваться в процессах как рыба в воде. Главное - не паниковать и методично применять правильные инструменты.