Conquering address already in use: Fixing EADDRINUSE and BindException Port Conflicts
You hit deploy and the service never comes up—just one red log line. Node yells EADDRINUSE, Java yells java.net.BindException, nginx yells bind() failed. The messages look different, but they all mean the same thing: someone is already holding the port you want. This post is a no-panic playbook: branch the cause into three cases and recover in five minutes with copy-paste commands for each OS.
Same collision, different error messages
Here are the errors the three runtimes spit out.
# Node.js
Error: listen EADDRINUSE: address already in use :::3000
# Java
java.net.BindException: Address already in use
# nginx
bind() to 0.0.0.0:80 failed (98: Address already in use)At the OS level, they all mean the bind() syscall returned EADDRINUSE(errno 98). In other words, the diagnosis procedure is the same no matter which language your server is written in.
Cause-branching table by error message
| Error message | Most common cause | Second thing to suspect |
|---|---|---|
EADDRINUSE :::3000 (Node) | Previous dev server/nodemon still alive | Duplicate start caused by hot reload |
java.net.BindException (Java) | Duplicate run of the same JAR/IDE instance | TIME_WAIT leftover on restart + SO_REUSEADDR not set |
bind() to 0.0.0.0:80 failed (98) (nginx) | nginx/Apache already running and occupying 80 | Ports 80/443 require root (don't confuse this with a permission issue) |
Three-way diagnosis decision tree
Before you recover, figure out which case you're in.
- An already-running process is occupying the port → Most common.
ss/lsoffinds a PID. → Kill that process. - TIME_WAIT leftover from a closed socket → You just stopped the server and started it again immediately. No PID, but the port is blocked. →
SO_REUSEADDRor wait a bit. - Restart fails because SO_REUSEADDR is not set → Companion of #2. If it keeps happening in your restart script, fix it in code/config.
How to tell them apart: if ss -ltnp shows a LISTEN-state PID, it's case 1. If nothing shows but ss -tan state time-wait has that port piled up, it's case 2 or 3.
Find and kill the occupying PID by OS (copy-paste ready)
Linux
These days ss is the standard over netstat (plenty of servers don't have net-tools installed).
# 1. Check LISTEN process + PID (recommended)
sudo ss -ltnp | grep :8080
# 2. Check with lsof
sudo lsof -i :8080
# 3. netstat (legacy environments)
sudo netstat -tlnp | grep 8080
# 4. Kill the port-occupying process in one shot
sudo fuser -k 8080/tcpmacOS
macOS has neither ss nor fuser. Find the process with lsof and kill it yourself.
# Check occupying PID (-n: skip DNS, -P: show port numbers as-is)
lsof -nP -i :8080
# Kill
kill -9 <PID>Windows
:: Check occupying PID (rightmost column is PID)
netstat -ano | findstr :8080
:: Force kill
taskkill /PID <PID> /FRoot-cause fixes and preventing recurrence
Handling leftover TIME_WAIT
Collisions from stopping the server and starting it again immediately are almost always fixed by SO_REUSEADDR.
// Node.js — usually the default, but you can make it explicit via the options object
const server = require('http').createServer(app);
server.listen({ port: 3000, host: '0.0.0.0' });// Java raw socket
ServerSocket socket = new ServerSocket();
socket.setReuseAddress(true); // must be called before bind
socket.bind(new InetSocketAddress(8080));Spring Boot's embedded Tomcat enables SO_REUSEADDR by default, so if collisions keep happening, first check that the port in application.yml isn't overlapping with another instance.
# Check leftover TIME_WAIT count
ss -tan state time-wait | wc -l# Kernel tuning (note: tcp_tw_reuse applies only on the client/outbound side)
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
# Shorten wait time after FIN
sudo sysctl -w net.ipv4.tcp_fin_timeout=30One common misconception.
tcp_tw_reusedoes not clear TIME_WAIT on a LISTEN server port. The right answer for server-side restart collisions isSO_REUSEADDR, not kernel tuning. I've seen plenty of people turn ontcp_tw_reusein production, see no effect, and waste time.
When the previous instance doesn't die on a systemd restart
If you have Restart=always but the main process dies leaving children behind, those children keep the port and the new instance collides trying to start. Set KillMode=mixed so the whole process group is cleaned up.
[Service]
ExecStart=/usr/bin/node /app/server.js
ExecStop=/bin/kill -SIGTERM $MAINPID
Restart=always
KillMode=mixed # SIGTERM to main, SIGKILL to remaining children
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target# Check shutdown failure via status and logs
systemctl status myapp
journalctl -u myapp -n 50Zombie and orphan processes, and containers
# Trace the app and parent PID (PPID)
ps -ef | grep myappA Z state (zombie) will not die from kill -9. Zombies disappear only when the parent reaps them with wait(), so you need to terminate the parent process. If you run the app directly as PID 1 in a container, zombies pile up because nothing reaps them—use the --init option or tini. In Kubernetes, Pods with overlapping hostPort on the same node can also collide, so check whether you're using a Service instead of hostPort.
Wrap-up: checklist when a collision happens
ss -ltnp | grep :PORT— is there a LISTEN PID?- If yes → kill the process (
fuser -k,kill,taskkill) - If no PID but still blocked → check TIME_WAIT (
ss -tan state time-wait) - If it repeats on every restart → set
SO_REUSEADDR/ systemdKillMode=mixed - If the dev server won't die in CI/hot-reload → clean up the process group in the shutdown hook
FAQ
Q. I killed it with kill -9 but the port is still blocked.
A. If no PID shows up, it's likely leftover TIME_WAIT rather than a process collision. Check with ss -tan state time-wait, and if it's a restart collision, enable SO_REUSEADDR. It usually clears itself within a few tens of seconds.
Q. I get ss: command not found on macOS.
A. macOS has neither ss nor fuser. Find the PID with lsof -nP -i :PORT, then kill it with kill -9 <PID>.
Q. Bind fails on ports 80/443 but nothing is occupying them.
A. Ports below 1024 require root. It may be a permission issue—run with sudo, or bind to an unprivileged port and put a reverse proxy in front.
Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
Comments
Be the first to comment.