Fixing "Address already in use" - Port Conflicts
What You'll Learn
- Why
Address already in use(EADDRINUSE) appears - How to identify the process holding the port and release it safely
- How to isolate the TIME_WAIT case where the error returns "even though I just stopped it"
Quick Summary
- Find who holds it:
sudo ss -lntp | grep ':PORT '(orsudo lsof -i :PORT) - Stop it correctly:
systemctl stopfor services,kill PIDfor one-off processes - Error but no process: it's TIME_WAIT — set
SO_REUSEADDRin the app or wait ~60s
Prerequisites
- OS: Ubuntu (systemd environment)
- Target: Anyone who hit
Address already in usestarting their own server/app - Examples use port
8080(substitute your own)
What Is "Address already in use"?
Conclusion: The port your app tries to reserve with
bind()is already held by another process, so the OS refuses the reservation.
A server process declares "listen on this port" at startup via the bind() system call. The same IP/port pair cannot be reserved twice, so if it is already taken, bind() returns EADDRINUSE.
Typical messages:
Error: listen EADDRINUSE: address already in use :::8080 bind: Address already in use OSError: [Errno 98] Address already in use
Errno 98 is the Linux number for EADDRINUSE. The cause is identical across languages and frameworks.
Why Does the Port Conflict?
Conclusion: Almost every case is one of three: an old process still alive, a double start, or TIME_WAIT from a just-killed process.
- Old process still running: you thought you restarted, but the old one survived. Easy to miss with detached starts (
&/ nohup / containers) - Double start: the same app launched twice, or another app uses the same port
- TIME_WAIT: connections from the just-terminated process linger in TCP
TIME_WAIT, and an app withoutSO_REUSEADDRfails to rebind
Cases 1 and 2 are solved by stopping the process. Only case 3 has no process to stop and needs a different approach (below).
How Do I Find Which Process Uses the Port?
Conclusion:
ss -lntpis the fastest way to get the listener and its PID.lsof/fusershow the same data differently.
Find the listener with ss (recommended)
sudo ss -lntp | grep ':8080 '
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=18))
users:(("node",pid=12345,...))— pid=12345 is the owner-llistening /-nnumeric /-tTCP /-pprocess (needssudo)
Confirm with lsof
sudo lsof -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 12345 hide 18u IPv4 98765 0t0 TCP *:8080 (LISTEN)
Get just the PID with fuser
sudo fuser 8080/tcp
8080/tcp: 12345
ss also shows whether the bind is 127.0.0.1:8080 or 0.0.0.0:8080. The same port number on 127.0.0.1 versus 0.0.0.0 is treated separately, which helps judge whether a real conflict exists.
How Do I Stop the Process and Release the Port?
Conclusion: Use
systemctl stopfor managed services andkill PIDfor manual processes.kill -9is a last resort.
When it runs as a service
sudo systemctl stop myapp.service
If you kill a systemd-managed process directly, an auto-restart (Restart= setting) may revive it and grab the port again. Always use systemctl stop.
When it is a manual process
Target the PID you found, trying the normal signal (SIGTERM) first:
sudo kill 12345
Only if it survives after a few seconds, force-kill it (SIGKILL):
sudo kill -9 12345
kill -9 gives the process no chance to clean up (close connections, remove temp files) and kills it instantly. It can cause data corruption or leftover lock files, so try kill (SIGTERM) first and use -9 only as a last resort.
Then confirm the port is free:
sudo ss -lntp | grep ':8080 '
No output means it is released.
Error but No Process (TIME_WAIT)?
Conclusion: Connections from the just-stopped process linger in
TIME_WAIT. WithSO_REUSEADDRthe app can restart instantly; without it, the state clears itself in ~60s.
If ss -lntp finds no listener yet Address already in use appears, suspect TIME_WAIT. Check:
ss -tan state time-wait | grep ':8080'
TIME_WAIT is a normal TCP state that prevents stray packets from being misread, and it usually clears in about 60 seconds. Options:
- Enable
SO_REUSEADDRin the app (recommended, permanent fix): most server implementations set this by default; a custom server should set it on the listen socket - Wait ~60s and restart: a stopgap
Do not casually change net.ipv4.tcp_tw_reuse or tcp_tw_recycle. These concern TIME_WAIT reuse on the outbound side, not the correct fix for a listening-port Address already in use. tcp_tw_recycle breaks connections behind NAT and has already been removed from the kernel. For the listen side, SO_REUSEADDR is the answer.
How Do I Prevent Recurrence?
Conclusion: Implement graceful shutdown and
SO_REUSEADDRin the app, and make the startup script reliably stop the old process.
- Set
SO_REUSEADDR: avoids restart failures caused by TIME_WAIT - Graceful shutdown: on SIGTERM, close connections before exiting; reduces reliance on
kill -9 - Delegate process management to systemd / containers: manual
&starts easily leave old processes behind - Pre-start check: at the top of the startup script, run
ss -lntp | grep ':PORT 'and stop any holder before launching
Things to Avoid
Conclusion: "Jump straight to
kill -9", "dodge by changing the port", and "enabletcp_tw_recycle" all breed recurrence and new failures.
Don't: Spam kill -9 without finding the cause
If the holder is a systemd-managed service, kill only triggers a restart. Identify it with ss first, then systemctl stop.
Don't: Dodge by Changing the Port Number
Switching 8080 to 8081 every time it conflicts destroys your sense of "what runs on which port". Identify and release the holder instead.
Don't: Enable tcp_tw_recycle
It lingers in old blog posts, but it breaks connectivity behind NAT and has been removed from current kernels. Use SO_REUSEADDR on the listen side.
Copy-Paste: From Identify to Release
# 1. Identify the holder sudo ss -lntp | grep ':8080 ' sudo lsof -i :8080 # 2-a. If it is a service sudo systemctl stop <service> # 2-b. If it is a manual process (SIGTERM -> last resort SIGKILL) sudo kill <PID> sudo kill -9 <PID> # 3. Confirm release sudo ss -lntp | grep ':8080 ' # Error but no process (TIME_WAIT) ss -tan state time-wait | grep ':8080'