Guía
Puerto 3000 de Next.js ocupado: fijar el puerto y encontrar el servidor que quedó vivo
14 de septiembre de 20263 min de lectura
Next.js no se detiene cuando el puerto 3000 está tomado. Te avisa que está usando otro y sigue:
⚠ Port 3000 is in use, trying 3001 instead.
Cómodo, y el origen de una tarde de confusión muy específica.
Por qué cambiarse en silencio es un problema
La mitad de tu configuración asume el 3000. Un proveedor de OAuth con http://localhost:3000/api/auth/callback en su lista permitida. Una configuración de CORS en tu API. Un NEXTAUTH_URL en el .env. Un reenviador de webhooks de Stripe. El marcador de un compañero.
Mueve la app al 3001 y ninguno de esos la sigue. Los síntomas no son “puerto equivocado”, son “el login está roto” y “la API devuelve errores de CORS”, que te mandan a buscar en el lugar completamente equivocado.
Así que cuando algo que funcionaba ayer se rompe hoy, lee las primeras líneas de la salida de tu servidor antes de depurar cualquier otra cosa.
Fija el puerto
Dile a Next qué puerto quieres:
next dev -p 3000
O déjalo permanente en package.json para que nadie tenga que acordarse:
{
"scripts": {
"dev": "next dev -p 3000"
}
}
Esto igual se cambia si el 3000 está tomado, así que se trata de ser explícito más que de forzar una falla. La protección real es darte cuenta, que es la parte siguiente.
Encuentra y libera lo que ocupa el 3000
lsof -i :3000
Y después:
lsof -ti :3000 | xargs kill
-t imprime solo los IDs de proceso y xargs se los pasa todos a un kill, lo que importa porque un servidor de desarrollo de Next puede involucrar más de un proceso. Un kill normal manda SIGTERM y lo deja cerrar; agrega -9 solo si te ignora.
Los sospechosos habituales
Otra app de Next. Todo proyecto de Next usa el 3000 por defecto, así que dos clones chocan por diseño. Dale a cada proyecto un puerto fijo y distinto en package.json y esto deja de pasar.
Una corrida anterior que no salió. Cerrar la terminal en vez de apretar Ctrl+C es el camino común. Un crash durante un hot reload también.
Turbopack o el worker de build que quedó dando vueltas. Después de un crash duro puedes quedar con un proceso node ocupando el puerto sin ninguna terminal visible asociada.
Docker. Si desarrollas la app en contenedor, un contenedor publicando el 3000 lo ocupa desde fuera de Node por completo. Revisa antes de matar:
docker ps --filter "publish=3000"
Si eso devuelve un contenedor, deténlo con docker stop. Matar docker-proxy no logra nada, Docker simplemente lo vuelve a poner.
Cuando el 3000 está ocupado pero nada escucha
De vez en cuando obtienes el conflicto y lsof -i :3000 no imprime absolutamente nada. Eso es TIME_WAIT, un estado de TCP donde una conexión recién cerrada mantiene el puerto reservado unos treinta segundos. No hay proceso que matar. Espera, o usa otro puerto por un minuto.
Lo cubrimos en detalle en qué significa realmente EADDRINUSE.
Revisar antes de que te muerda
La costumbre más barata es mirar el puerto antes de arrancar, en vez de después de que algo se rompa:
lsof -i :3000
Si no imprime nada, estás despejado.
Bosun mantiene esa respuesta en la barra de menú de forma permanente, con el proceso, contenedor o túnel detrás de cada puerto nombrado como corresponde. macOS 14 o posterior, prueba de 14 días, sin cuenta.
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.