← Volver al blog

Guía

¿Puerto 3000 ocupado en tu Mac? Así se soluciona de verdad

15 de julio de 20264 min de lectura

Corres npm run dev, rails server o python manage.py runserver, y te aparece algo como:

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

Algo ya está escuchando en el puerto 3000. macOS solo permite que un proceso ocupe un puerto a la vez, sin excepciones, así que tu servidor nuevo no puede arrancar hasta que lo que está ahí se vaya.

Qué significa realmente “address already in use”

Tu app no está rota. Es el sistema operativo aplicando una regla básica: un puerto, un proceso escuchando. Casi siempre el culpable es una ejecución anterior del mismo servidor de desarrollo que no cerró bien, un proceso en segundo plano de otro proyecto usando el mismo puerto por defecto, o (menos frecuente) algo sin relación que justo está escuchando ahí.

Paso 1: averigua qué está ocupando el puerto

Abre Terminal y corre:

lsof -i :3000

lsof lista archivos abiertos, y en Unix “todo es un archivo”, incluyendo los sockets de red. Vas a ver algo así:

COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    41213    ben   22u  IPv4 0x...      0t0  TCP *:3000 (LISTEN)

Las dos columnas que importan: COMMAND (qué está corriendo) y PID (su identificador de proceso, único por proceso activo). Si el puerto pertenece a un proceso que no tienes permiso de ver, agrega sudo:

sudo lsof -i :3000

lsof -i :3000 no muestra nada, pero el puerto sigue ocupado. Dos causas habituales. Primero, un problema de permisos que ni sudo cubrió, en cuyo caso amplía la búsqueda:

sudo lsof -iTCP -sTCP:LISTEN -P -n | grep 3000

Segundo, un mismatch IPv4/IPv6: algo asociado a [::1]:3000 (IPv6) no va a aparecer si consultas solo la forma IPv4. Prueba lsof -i6:3000 explícitamente para revisar.

Paso 2: mátalo de la forma correcta

Ya tienes el PID. Ahora, no vayas directo a kill -9, hay un orden correcto:

kill -15 41213

kill -15 envía SIGTERM, una señal educada de “por favor ciérrate”. Un proceso bien comportado la captura, cierra sus conexiones a la base de datos, guarda cualquier escritura pendiente, y termina limpio. Dale un segundo.

Si sigue ahí, escala:

kill -9 41213

kill -9 envía SIGKILL, que el sistema operativo fuerza de inmediato, sin limpieza, sin que el proceso pueda objetar. Siempre funciona, pero puede dejar cosas en mal estado: un lock de archivo abierto, una escritura sin terminar, un caché a medio escribir. Úsalo cuando SIGTERM no funcionó, no como opción por defecto.

El one-liner que todos copian y pegan

Lo vas a ver en todos lados:

lsof -ti :3000 | xargs kill -9

-t le dice a lsof que solo devuelva el PID (sin encabezado, sin columnas extra), y xargs kill -9 lo alimenta directo a kill. Es rápido y funciona, pero ahora sabes qué hace realmente, y que se salta por completo el paso de SIGTERM. Está bien para un servidor de desarrollo que no te importa; menos bien para algo que está a mitad de escribir un archivo.

Por qué esto sigue pasando

Los sospechosos habituales:

  • Un servidor de desarrollo que crasheó y no liberó el socket. Ctrl+C normalmente limpia bien, pero un crash duro o un proceso suspendido (Ctrl+Z en vez de Ctrl+C) puede dejarlo ocupando el puerto.
  • Un proceso zombie de una sesión anterior que olvidaste que seguía corriendo en otra pestaña de terminal.
  • Un contenedor Docker publicando el mismo puerto (docker ps lo va a mostrar si es así; usa docker stop <contenedor> en vez de matar el proceso del host).
  • Dos proyectos con el mismo puerto por defecto (Next.js, Vite, Rails y Express usan por defecto un puñado de puertos comunes).

Cuando ni siquiera es tu servidor de desarrollo

Si lsof -i :5000 o :7000 muestra ControlCenter como dueño, no es un proceso rebelde, es macOS mismo. Desde Monterey, AirPlay Receiver escucha en esos puertos por defecto. Desactívalo en Ajustes del Sistema → General → AirDrop y Handoff → Receptor AirPlay si necesitas esos puertos libres, o simplemente elige otro puerto para tu servidor.

Preguntas rápidas

¿Puerto 8080 ocupado, misma solución? Sí, el mismo proceso, solo cambia el número de puerto: lsof -i :8080, y luego kill -15 (o -9) sobre el PID que encuentres.

¿kill -9 o kill -15, cuál uso realmente? Siempre prueba primero -15 (SIGTERM), espera un segundo, y solo usa -9 (SIGKILL) si el proceso sigue vivo. -15 le da al proceso la chance de limpiar; -9 no.

¿Esto pasa específicamente con Node? El error EADDRINUSE es un mensaje de Node.js/libuv, pero la causa y la solución son idénticas para Rails, Django, Vite o cualquier otra cosa que ocupe un puerto TCP en macOS.

¿Funciona igual para otros puertos por defecto, como el 5173 de Vite o el 8000 de Django? Sí, sin cambios. Cambia el número de puerto en lsof -i :PUERTO y estás siguiendo exactamente los mismos pasos, sea Vite (5173), Angular (4200), Django (8000) o cualquier otro.

Ve esto en vez de escribirlo

Bosun vive en tu barra de menú y muestra cada puerto abierto en tu Mac, en vivo, mapeado al proceso detrás. Un clic para matarlo, SIGTERM primero. Útil la primera vez que pasa esto. Realmente útil la quinta vez que pasa en una misma tarde.