lenec ru

← все посты

EADDRINUSE :::3000 — порт занят, что делать

12K

Запускаешь npm run dev, а вместо знакомого ready on http://localhost:3000 получаешь:

Error: listen EADDRINUSE: address already in use :::3000

Порт 3000 уже кем-то занят. Чаще всего это твой же предыдущий процесс Next или Vite, который не закрылся как следует. Реже — другой инструмент, который тоже любит этот порт. У меня в практике это бывает раз в неделю на каждом проекте.

Покажу, как быстро понять, кто держит порт, и как от этого избавиться. И заодно — как сделать, чтобы это меньше мешало.

Кто занимает порт

На macOS и Linux

Команда lsof отдаёт список процессов, слушающих указанный порт:

lsof -i :3000

Вывод примерно такой:

COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node    23415   me   23u  IPv6 0x...                0t0  TCP *:3000 (LISTEN)

Видим: процесс node с PID 23415 держит порт.

Можно ещё через ss:

ss -ltnp | grep :3000

Эта команда работает на современных Linux. На macOS её нет, там lsof плюс netstat.

На Windows

netstat -ano | findstr :3000

В последней колонке будет PID. Дальше:

tasklist /FI "PID eq 23415"

Убиваем процесс

На macOS/Linux:

kill 23415

Сначала пробую SIGTERM (это и есть kill без флагов). Большинство Node-процессов корректно завершатся, дописав логи. Если процесс не реагирует:

kill -9 23415

Это SIGKILL — без шансов на корректное завершение. Использую только если обычный kill не помог секунд за 5.

Один лайнер, чтобы не возиться:

lsof -ti :3000 | xargs kill -9

На Windows:

taskkill /PID 23415 /F

Что обычно держит 3000-й порт

Зомби-процесс от вчерашнего dev-сервера

Самая частая ситуация. Терминал закрылся, ноутбук уснул, на Mac ты ушёл в режим энергосбережения, и Next не успел корректно отвалиться. Если в lsof видишь node или npm — это он.

Несколько проектов одновременно

Открыл два терминала, в каждом — свой Next-проект. Первый занял 3000, второй ругается. Сам Next в новых версиях (13+) умеет автоматически выбирать следующий свободный порт и сообщает об этом в консоли. Если у тебя нет автоподбора — добавляй в package.json:

{
  "scripts": {
    "dev": "next dev -p 3001"
  }
}

Системные сервисы и docker-контейнеры

Бывает, что 3000 занимает совсем не Node. Например:

  • контейнер с Grafana или другим инструментом, который ты забыл остановить (docker ps);
  • локально установленный сервис, который кто-то поставил в systemd.

Если в lsof видишь com.docker.backend или подобное — иди в Docker. Останови контейнер:

docker ps
docker stop <container_id>

WSL и Hyper-V на Windows

На Windows иногда порт «занят», хотя ничего не запущено. Виноват резервированный диапазон Hyper-V — Windows бронирует под виртуализацию случайные порты после ребута. Помогает:

net stop winnat
net start winnat

Это перезапускает службу NAT и обычно освобождает порт.

Как уменьшить количество таких ситуаций

Не убивать терминал, а останавливать процесс

Привыкаешь нажимать Ctrl+C в терминале с dev-сервером, прежде чем закрывать. Звучит банально, но половина зависших Node-процессов именно отсюда.

Использовать переменную PORT

Я в каждом проекте задаю порт через .env:

PORT=3030

Next и Vite оба читают эту переменную и стартуют на ней. Удобно, когда у тебя 4 проекта и ты помнишь — этот всегда на 3030, тот на 3040.

Скрипт «убить и запустить»

Если работаю в команде, где половине людей всё ещё лень убивать процессы вручную, добавляю в package.json:

{
  "scripts": {
    "dev": "next dev",
    "dev:fresh": "kill-port 3000 && next dev"
  }
}

Пакет kill-port ставится в devDependencies, работает и на Windows, и на Mac. На своих проектах хватает dev, для тех, кто часто ловит EADDRINUSE — есть dev:fresh.

Если EADDRINUSE прилетает в проде

Это уже другая история. На сервере под systemd или pm2 порт занимает предыдущая копия твоего сервиса. Симптомы — после деплоя сервис не поднимается, в логе EADDRINUSE.

Лечится правильным управлением жизненным циклом:

  • в systemd-юните ExecStop и KillMode=mixed, чтобы старый процесс дожидался завершения перед стартом нового;
  • в pm2 — pm2 reload вместо pm2 start при апдейте;
  • в Docker-сетапах — нормальный graceful shutdown в самом приложении (по SIGTERM закрывать сервер).

Если в коде ты делаешь process.on('SIGTERM', () => server.close(...)), шанс схлопотать EADDRINUSE на деплое сильно снижается.

Маленький чек-лист

  • lsof -i :3000 или ss -ltnp | grep :3000 — кто держит порт.
  • kill PID — корректно убить.
  • kill -9 PID — если совсем висит.
  • Поменять порт через PORT=... или -p 3001.
  • На Windows — net stop winnat && net start winnat, если порт «занят», но процесса нет.

EADDRINUSE — почти никогда не баг твоего кода. Это либо хвост старого dev-сервера, либо чужой процесс на нужном порту. Минута расследования — и снова можно работать.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.