Réponse courte : pour tuer le processus qui occupe un port, identifiez d'abord son PID, puis arrêtez-le. Sous macOS et Linux : lsof -ti :3000 | xargs kill (et kill -9 seulement si le processus résiste). Sous Linux avec psmisc installé, fuser -k 3000/tcp fait les deux en une commande. Sous Windows : netstat -ano | findstr :3000 puis taskkill /PID <pid> /F. Dans un conteneur Docker, ce n'est pas un kill qu'il faut mais docker stop.
Le message est toujours le même : Error: listen EADDRINUSE: address already in use :::3000 au lancement de next dev, ou bind: address already in use sur un kubectl port-forward. Un processus tient encore le port — souvent un serveur de développement lancé dans un onglet de terminal fermé trop vite, parfois un conteneur oublié, parfois un tunnel SSH. Voici les commandes que nous utilisons au quotidien, les différences entre systèmes, et les trois cas où tuer le processus est une mauvaise idée.

Comment tuer le processus qui occupe un port ?
En deux temps : trouver le PID qui écoute sur le port, puis envoyer un signal d'arrêt à ce PID. La commande dépend du système.
| Système | Identifier | Tuer | |
|---|---|---|---|
| macOS | lsof -i :3000 | kill $(lsof -ti :3000) | |
| Linux (moderne) | ss -ltnp 'sport = :3000' | kill <pid> | |
Linux (psmisc) | fuser 3000/tcp | fuser -k 3000/tcp | |
| Windows (cmd) | `netstat -ano \ | findstr :3000` | taskkill /PID <pid> /F |
| Windows (PowerShell) | Get-NetTCPConnection -LocalPort 3000 | Stop-Process -Id <pid> | |
| Docker | docker ps --filter publish=3000 | docker stop <nom> |
Le détail qui compte : lsof -t ne sort que les PID, ce qui le rend directement composable avec kill. Sur macOS, lsof -ti :3000 est la forme la plus courte qui fonctionne sans sudo pour vos propres processus. lsof est documenté dans la page de manuel des outils Apple et livré par défaut sur macOS ; sous Linux, il n'est plus toujours installé, d'où l'intérêt de connaître ss, fourni par iproute2.
La version Linux sans rien installer
ss fait partie du paquet de base de la plupart des distributions depuis l'abandon de net-tools :
# Qui écoute sur 3000 ? (-l listen, -t tcp, -n numérique, -p process)
sudo ss -ltnp 'sport = :3000'
# LISTEN 0 511 *:3000 *:* users:(("next-server",pid=48213,fd=23))
kill 48213
Sans sudo, la colonne users: reste vide pour les processus qui ne vous appartiennent pas : vous voyez le port occupé mais pas le coupable. C'est la cause numéro un de « ma commande ne trouve rien alors que le port est pris ».
L'alias à garder
Nous mettons systématiquement cette fonction dans les dotfiles des postes de dev, parce qu'elle évite le copier-coller d'un PID :
# ~/.zshrc ou ~/.bashrc
killport() {
local pid
pid=$(lsof -ti ":$1" -sTCP:LISTEN)
if [ -z "$pid" ]; then
echo "Aucun processus en écoute sur le port $1"
return 1
fi
echo "Arrêt du PID $pid sur le port $1"
kill "$pid" || kill -9 "$pid"
}
Le -sTCP:LISTEN est important : sans lui, lsof -ti :3000 remonte aussi les connexions sortantes vers le port 3000 d'une autre machine. Vous tueriez alors votre navigateur ou votre client HTTP au lieu du serveur.
Pourquoi ne pas faire directement kill -9 ?
Parce que kill -9 envoie SIGKILL, que le processus ne peut pas intercepter : pas de fermeture de connexions, pas de vidage de buffers, pas de nettoyage de fichiers temporaires. Sur un serveur de développement Next.js, c'est anodin. Sur trois autres cas, non.
- Une base de données qui écoute sur 5432 ou 3306 : un SIGKILL force une récupération au redémarrage. PostgreSQL le gère (c'est l'équivalent d'une coupure de courant), mais l'arrêt propre reste
pg_ctl stopousystemctl stop postgresql. La documentation PostgreSQL sur l'arrêt du serveur détaille les modessmart,fastetimmediate; SIGKILL est pire queimmediate. - Un processus avec des enfants :
killsur le parent peut laisser les enfants orphelins qui gardent le port (classique avecnpm run devqui lance un sous-processus). Tuez le groupe :kill -- -$(ps -o pgid= <pid> | tr -d ' '). - Un conteneur : le PID vu depuis l'hôte est celui du processus dans le conteneur. Le tuer laisse Docker dans un état incohérent ;
docker stopenvoie SIGTERM puis SIGKILL après le délai de grâce (10 secondes par défaut).
L'ordre correct est donc toujours : SIGTERM (kill), on attend deux secondes, et SIGKILL (kill -9) seulement si le port est encore occupé.
Le port est libre mais le bind échoue quand même
Trois causes, toutes fréquentes, et aucune ne se règle avec un kill.
1. État TIME_WAIT. Après la fermeture d'une socket, le noyau garde le quadruplet en TIME_WAIT pendant environ 60 secondes (2×MSL sous Linux). Un serveur qui ne positionne pas l'option SO_REUSEADDR refusera de se relancer pendant ce délai. Vérifiez avec ss -tan state time-wait '( sport = :3000 )'. La seule réponse correcte est d'attendre, ou de corriger le serveur — pas de toucher à net.ipv4.tcp_tw_reuse.
2. Port réservé par Windows. Hyper-V, WSL2 et le Windows NAT Service réservent des plages de ports dynamiques au démarrage, et le port 3000 ou 5000 peut tomber dedans. netstat ne montre alors aucun processus. Listez les exclusions :
netsh interface ipv4 show excludedportrange protocol=tcp
Si votre port est dans une plage exclue, changez de port ou réservez-le explicitement avec netsh int ipv4 add excludedportrange. C'est le symptôme typique de « ça marchait hier » après un redémarrage de Windows.
3. Permissions sur les ports < 1024. Sous Linux, se binder sur 80 ou 443 demande CAP_NET_BIND_SERVICE ou root. L'erreur est EACCES, pas EADDRINUSE — lisez le code d'erreur avant de chercher un processus.

Et en Docker, Kubernetes ou CI ?
Le réflexe kill ne s'applique pas : dans ces environnements, le port est tenu par un objet géré par un orchestrateur, et c'est l'orchestrateur qu'il faut solliciter.
- Conteneur Docker qui publie le port.
docker ps --filter publish=3000donne le conteneur ;docker stop <nom>l'arrête proprement. Sidocker psne montre rien, cherchez les conteneurs arrêtés mais avec un port encore mappé par le proxy :docker ps -apuis redémarrez le démon en dernier recours. kubectl port-forwardfantôme. Unport-forwardlancé en arrière-plan survit à la fermeture du terminal.lsof -ti :8080 -sTCP:LISTENremonte un processuskubectl: c'est celui-là qu'il faut tuer, pas un pod. Les bonnes pratiques d'accès aux services de la documentation Kubernetes rappellent queport-forwardest un outil de debug, pas un mode d'exposition.- Port déjà pris dans un pod. Deux conteneurs d'un même pod partagent le namespace réseau : deux
containerPort: 3000dans le même pod donnent unEADDRINUSEau deuxième. Le diagnostic se fait dans les logs du conteneur, pas sur l'hôte. - Runner GitLab CI. Sur un runner partagé, un job précédent peut avoir laissé un service en écoute. Plutôt que de tuer à l'aveugle, faites écouter vos jobs sur un port dynamique (
PORT=0quand le serveur le supporte) ou utilisez les services GitLab CI qui obtiennent leur propre réseau. C'est le même raisonnement que celui tenu dans notre pipeline GitLab CI/CD pour Next.js sur Kubernetes : un job qui dépend de l'état du runner n'est pas reproductible.
Comment éviter le problème plutôt que le subir ?
En rendant le port non conflictuel par construction. Quatre habitudes qui suppriment 90 % des occurrences :
- Un port par projet, écrit dans le
.envdu dépôt et non dans la tête des développeurs : 3001 pour l'app A, 3002 pour l'app B. Next.js acceptenext dev -p 3001ouPORT=3001. - Arrêter avec Ctrl+C, jamais en fermant l'onglet. Fermer la fenêtre du terminal n'envoie pas toujours SIGHUP au groupe de processus selon l'émulateur et le shell ; c'est l'origine la plus banale des serveurs orphelins.
docker compose downau lieu deCtrl+Csur uncompose up: le premier retire aussi le réseau et les mappings de ports.- Un script
predevqui libère le port avant de démarrer, pour les projets d'équipe :
{
"scripts": {
"predev": "lsof -ti :3000 -sTCP:LISTEN | xargs -r kill || true",
"dev": "next dev"
}
}
Le -r de xargs (GNU) évite d'appeler kill sans argument quand le port est libre ; sur macOS, utilisez xargs sans -r avec le || true.

Le cas particulier du port occupé en production
En production, un port occupé n'est pas un désagrément de poste de dev : c'est un déploiement bloqué. Le scénario classique est un rolling update où l'ancien pod n'a pas encore libéré son port alors que le nouveau démarre. Sur Kubernetes, avec hostPort ou hostNetwork: true, le conflit est garanti : le port est pris sur le nœud, pas dans le pod. La réponse n'est pas de tuer un processus mais de supprimer hostPort et de passer par un Service plus un ingress ou une Gateway — le mécanisme décrit dans notre article sur la Gateway API en remplacement d'ingress-nginx.
Sur une VM mono-serveur, le même conflit apparaît entre deux versions lancées par systemd. L'ordre correct est ExecStop puis ExecStart, avec un TimeoutStopSec supérieur au délai de fermeture des connexions de votre serveur HTTP (Node.js ne ferme pas les keep-alive avant server.close() plus la fin des requêtes en cours).
Questions fréquentes
Quelle est la commande « kill port » la plus courte ?
Sous macOS et Linux avec lsof installé : kill $(lsof -ti :3000 -sTCP:LISTEN). Sous Linux avec psmisc : fuser -k 3000/tcp. Sous Windows : taskkill /PID <pid> /F après avoir lu le PID dans netstat -ano | findstr :3000. Il n'existe pas de commande killport native sur ces systèmes ; c'est toujours une composition de deux outils.
Pourquoi lsof -i :3000 ne renvoie rien alors que le port est occupé ?
Trois raisons, dans l'ordre de fréquence : vous n'êtes pas root et le processus appartient à un autre utilisateur (relancez avec sudo) ; le port est tenu par un conteneur dont le mapping passe par le proxy Docker (cherchez avec docker ps) ; le port est dans une plage réservée par Windows/WSL2 (netsh interface ipv4 show excludedportrange).
Faut-il un redémarrage pour libérer un port ?
Non, sauf cas pathologique. Un port reste bloqué soit par un processus (tuable), soit par un état TIME_WAIT (qui expire seul en une minute environ), soit par une réservation système (modifiable sans redémarrage sous Windows avec netsh). Redémarrer la machine masque la cause et elle revient au prochain lancement.
Comment savoir ce que fait le processus avant de le tuer ?
ps -p <pid> -o pid,ppid,user,etime,command donne la ligne de commande complète, l'utilisateur et le temps d'exécution. Un etime de plusieurs jours sur un poste de dev signale presque toujours un serveur orphelin ; un processus lancé il y a trente secondes est peut-être votre collègue en pair programming sur une session partagée.
En résumé
- Identifier avant de tuer :
lsof -ti :<port> -sTCP:LISTEN(macOS/Linux),ss -ltnp(Linux),netstat -ano | findstr(Windows). - SIGTERM d'abord (
kill), SIGKILL (kill -9) seulement en second recours — et jamais en premier sur une base de données. - Le filtre
-sTCP:LISTENévite de tuer une connexion sortante au lieu du serveur. - En Docker, Kubernetes ou CI, on n'utilise pas
kill:docker stop, suppression duport-forward, ou port dynamique dans le job. - Port libre mais bind refusé : pensez
TIME_WAIT, plages réservées Windows/WSL2, ou permissions sur les ports inférieurs à 1024. - Prévention : un port par projet dans le
.env, arrêt par Ctrl+C,docker compose down, scriptpredev.
Si ces conflits de ports arrivent aussi sur vos environnements de recette ou vos déploiements, c'est généralement le signe d'une exposition réseau construite à la main. Nous reprenons ce type d'architecture dans nos missions d'infrastructure et d'exploitation, et nous industrialisons les environnements de développement côté développement applicatif. Pour en parler sur un cas concret, la page contact est le chemin le plus court.


