'Address already in use' en Mac: soluciona EADDRINUSE y libera el puerto
El error address already in use (EADDRINUSE) significa que otro proceso ocupa el puerto. Cómo encontrarlo en macOS, liberarlo y evitar que se repita.
Levantas un servidor y muere de inmediato con bind: address already in use (o Address already in use, errno 48 en macOS). Node llama a este mismo fallo EADDRINUSE, Docker lo expresa como “port 3000 is already allocated”, y también lo verás simplemente como “puerto en uso”: son el mismo problema. El mensaje es directo pero exacto: algo ya ocupa el puerto al que intentas vincularte. Así lo encuentras y recuperas tu puerto.
Qué significa el error en realidad
Cuando un programa quiere escuchar en un puerto, llama a bind() sobre ese número de puerto. Si ya hay otro socket vinculado ahí, el sistema operativo lo rechaza con EADDRINUSE, “address already in use” (dirección ya en uso). En macOS es el errno 48 (en Linux es el 98, por si lo estás cruzando con otra fuente).
Hay dos causas distintas, y la solución cambia:
- Otro proceso ocupa realmente el puerto (el caso común).
- Una instancia anterior de tu propio programa dejó el puerto en
TIME_WAIT(el caso engañoso).
Si ves EADDRINUSE desde Node.js
La versión de Node de este error se ve así:
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (node:net:...)
El mismo fallo, con más detalle. Cuando tu código llama a app.listen(3000), le pide al sistema operativo que se vincule al puerto 3000, y si algo ya ocupa ese puerto, el bind falla y Node muestra EADDRINUSE. El :::3000 del mensaje es solo la forma IPv6 de “puerto 3000 en todas las interfaces”. Nueve de cada diez veces en desarrollo, el culpable es una instancia anterior de tu propio servidor que no se cerró: un proceso caído, un nodemon atascado o una terminal que cerraste sin detener el servidor. La solución es la misma que el caso general de abajo.
Paso 1: Encuentra qué ocupa el puerto
Sustituye 3000 por tu puerto:
sudo lsof -i :3000 -n -P
Si algo lo ocupa, verás el proceso y el PID:
COMMAND PID USER ... NODE NAME
node 1421 aaron ... TCP *:3000 (LISTEN)
Ahora sabes que node, PID 1421, ocupa el puerto 3000. Si el nombre del proceso no te dice nada, consulta qué app usa un puerto en Mac para averiguar qué es en realidad.
Paso 2: Libera el puerto
Si ese proceso es seguro de detener (un servidor de desarrollo viejo, un script olvidado), termínalo:
# Primero de forma elegante; le permite limpiar
kill 1421
# Si no se va, fuérzalo
kill -9 1421
O hazlo de una sola vez sin copiar el PID:
kill -9 $(lsof -ti :3000)
El flag -t hace que lsof muestre solo el PID, que se pasa directamente a kill. Consulta matar un proceso por puerto en Mac para saber cuándo preferir el SIGTERM elegante al SIGKILL forzado. Una vez que lo hayas terminado, confirma que el puerto realmente quedó libre:
lsof -i :3000 -n -P
Un resultado vacío significa que está libre. Si algo sigue ahí, no terminaste lo que creías.
El caso TIME_WAIT: no hay nada en el puerto, pero sigue “en uso”
A veces lsof -i :3000 no devuelve nada y aun así obtienes “address already in use”. Esto suele ser TIME_WAIT: cuando una conexión TCP se cierra, el sistema operativo mantiene el socket reservado durante un breve enfriamiento (normalmente unos 30 segundos en macOS) para asegurarse de que ningún paquete rezagado de la conexión antigua se entregue por error a una nueva.
Puedes confirmarlo:
netstat -an | grep 3000
Si ves el puerto en TIME_WAIT en lugar de LISTEN, eso es lo que bloquea la revinculación. Dos formas de resolverlo:
- Simplemente espera. El estado se limpia solo, normalmente en unos 30 segundos en macOS.
- Haz que tu servidor reutilice la dirección. La mayoría de los servidores pueden activar la opción de socket
SO_REUSEADDR, que permite a un socket nuevo vincularse a un puerto aún enTIME_WAIT. En Node está activa por defecto; en muchos frameworks hay un flag de “reutilizar dirección”. Esta es la solución correcta a largo plazo para servidores que reinicias constantemente.
Paso 3: O simplemente cambia tu puerto
Si no puedes detener con seguridad lo que ocupa el puerto, apunta tu app a otro sitio:
# Node
PORT=3001 npm start
# Vite (en vite.config o por CLI)
npm run dev -- --port 5174
# Flask
flask run --port 5001
# Rails
rails server -p 3001
Esto funciona sin problemas cuando tu código lee el puerto del entorno, como debería hacer:
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Escuchando en ${port}`));
Mover tu app suele ser más seguro que matar un proceso que no puedes identificar al instante, sobre todo si el puerto pertenece a un servicio del sistema.
Evita que se repita
Unos cuantos hábitos eliminan la mayoría de los reincidentes, sobre todo en Node:
Maneja el error en lugar de caer a ciegas. Captúralo e imprime algo útil:
const server = app.listen(port);
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`El puerto ${port} ya está en uso. Detén el otro proceso o define otro PORT.`);
process.exit(1);
} else {
throw err;
}
});
Cierra de forma limpia con Ctrl-C para que el puerto se libere cuando detienes el servidor:
process.on('SIGINT', () => {
server.close(() => process.exit(0));
});
Usa un gestor de procesos como nodemon o pm2, que reinicia tu app y libera el puerto antiguo entre ejecuciones en lugar de dejar huérfanos.
Por qué sigue pasando
La causa raíz siempre es la misma: solo un programa puede escuchar en un puerto a la vez. Servidores caídos que no liberaron su puerto, dos herramientas que vienen por defecto en el 3000, un depurador aún conectado en segundo plano: todo produce el mismo error. La habilidad está en identificar rápido qué ocupa el puerto, que es justo lo que hacen los pasos anteriores.
Los culpables habituales
La mayoría de los errores de “address already in use” se reducen a una lista corta de sospechosos de siempre:
- 3000 → Node, React, Rails (puerto 3000 en uso)
- 5000 → Flask, y AirPlay Receiver en macOS (puerto 5000 en uso)
- 8080 → Tomcat, proxies, segundas apps web (qué es el puerto 8080)
- 5173 → Vite
- 5432 → PostgreSQL
Un detalle propio de macOS: los puertos 5000 y 7000 suelen estar ocupados por AirPlay Receiver, no por un programa que iniciaste tú. Si no encuentras tu proceso en el 5000, normalmente es por eso: desactiva AirPlay Receiver en Configuración del Sistema o simplemente usa otro puerto.
Encuentra al culpable al instante
Portie muestra cada proceso que ocupa un puerto en tu Mac en una sola tabla en vivo, así que cuando te topas con “address already in use”, ves exactamente qué hay en ese puerto sin escribir un comando lsof, y lo terminas con un clic.
El monitoreo local es gratuito. El desbloqueo de $8.99 (pago único) agrega la terminación de procesos con un clic (elegante o forzada) y el escaneo remoto de puertos. Descarga Portie y no vuelvas a descifrar el errno 48 a mano.