Address already in use: quién tiene el puerto y por qué kill -9 va al final
Cómo encontrar el proceso con ss, ver quién lo levantó y cortarlo sin kill -9. Y qué significa que esté escuchando en 0.0.0.0.
En este artículo
Pasa así: levantás tu app, la terminal te devuelve EADDRINUSE y no arranca. Buscás el error y la primera respuesta dice kill -9 $(lsof -t -i:3000). Lo corrés y anda.
Casi siempre. A veces el puerto vuelve a estar ocupado un segundo después. Otras, lo que mataste era la base de otro proyecto que tenías abierto. Y en el camino se pasa por alto algo que importa más que el error: ese proceso que no sabías que seguía corriendo también podía estar atendiendo a otras máquinas.
Quién tiene el puerto #
El error solo dice el puerto. Para saber quién lo tiene está ss, que viene en cualquier distro actual:
$ node -e "require('http').createServer().listen(3000)"
Error: listen EADDRINUSE: address already in use :::3000
$ ss -ltnp 'sport = :3000'
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 0.0.0.0:3000 0.0.0.0:* users:(("python3",pid=3365731,fd=3))-l muestra lo que está escuchando, -t TCP, -n números en vez de nombres y -p el proceso. La última columna es la respuesta: un python3 con PID 3365731. Si esa columna sale vacía, el proceso es de otro usuario y con sudo adelante aparece.
Con el PID, ps te dice qué es y hace cuánto corre:
$ ps -o pid,ppid,user,etime,cmd -p 3365731
PID PPID USER ELAPSED CMD
3365731 3365724 valen 00:01 python3 -m http.server 3000Acá dice un segundo porque lo levanté para el ejemplo; uno olvidado suele decir días. python3 -m http.server es un clásico: lo usaste para pasar un archivo y quedó corriendo en otra terminal, sirviendo la carpeta entera donde lo levantaste, archivos ocultos incluidos.
Qué quiere decir 0.0.0.0 #
Volvé a la columna Local Address. 0.0.0.0:3000 quiere decir que el proceso acepta conexiones por cualquier interfaz de la máquina: le puede hablar cualquiera que esté en tu red. Mandá pedidos y cambiá la dirección mientras llegan.
Cualquiera en tu red llega al puerto y se baja el .env. Desde internet no, mientras el router no lo deje pasar.
Lo probé con ese mismo servidor, levantado en una carpeta de prueba con un .env. Desde otra máquina de la red:
$ curl -s http://<ip>:3000/.env
OPENAI_API_KEY=sk-proj-demo-no-es-realLa key es de mentira y <ip> reemplaza la de mi máquina. Levantado con --bind 127.0.0.1, el mismo curl devuelve Couldn't connect to server. Y si lo corrés en tu home, la carpeta que se sirve incluye .ssh.
El error de Node de más arriba también lo dice: :::3000. Si no le pasás una dirección, listen(3000) escucha en ::, que son todas.
kill manda una señal #
kill no mata: le manda una señal al proceso. Si no le decís cuál, manda TERM, que le pide terminar y lo deja cerrar lo que tiene abierto. kill -9 manda KILL, que el programa no puede atender: el kernel lo saca en el acto. Mandá pedidos y, cuando quieras, mandale una señal. Después levantalo de nuevo y probá la otra.
Atiende pedidos y cada tanto guarda en el disco. Mandale una señal.
| Señal | Número | Qué pide | ¿La puede atender el proceso? |
|---|---|---|---|
HUP | 1 | Varios servicios la usan para releer la configuración | Sí |
INT | 2 | Es lo que manda Ctrl+C | Sí |
TERM | 15 | Que termine: cerrar lo que tiene abierto y salir | Sí |
KILL | 9 | Nada. El kernel lo saca en el momento | No |
El orden es kill, esperar unos segundos, y kill -9 solo si sigue ahí.
Si vuelve solo, es un servicio #
A veces matás el proceso y el puerto vuelve a estar ocupado al segundo. Lo reproduje con un servicio de systemd que se reinicia solo, y pasó exactamente eso. Mandá pedidos, matalo y mirá qué pasa con los que llegan mientras no está.
systemd vigila el servicio. Mandá pedidos y probá matar el proceso.
El padre de ese proceso es systemd, así que matarlo no sirve. systemctl status acepta un PID y te dice de qué servicio es:
$ systemctl --user status 3366835 | head -4
● demo-web.service - [systemd-run] /usr/bin/python3 -m http.server 3000 --bind 127.0.0.1
Loaded: loaded (/run/user/1000/systemd/transient/demo-web.service; transient)
Transient: yes
Active: active (running) since Fri 2026-09-25 11:21:28 -03; 1s agoCon eso sabés qué parar: systemctl --user stop demo-web. En un servidor lo normal es un servicio del sistema, y va sin --user. systemctl disable hace que no vuelva a arrancar con la máquina.
El criterio #
Antes de matar un proceso, fijate qué es y quién lo levantó: ss -ltnp y ps te lo dicen en dos líneas. Si vuelve solo, es un servicio y se para con systemctl stop.
Y mirá la dirección en la que escucha. Si dice 0.0.0.0 y no sabés por qué, arreglá eso antes que el error.
Lo próximo llega también por mail. Cada dos semanas, en Apuntes.
Comentarios
Pasan por moderación antes de aparecer.