# Fix "Address Already in Use" on Linux (Port Conflicts)

URL: https://www.devopsness.com/blog/fix-address-already-in-use
Author: Kiril Urbonas
Published: 2026-07-19
Updated: 2026-10-03
Categories: Linux
Tags: Linux, Networking, Troubleshooting, Sysadmin, Ports

Summary: Track down what owns a busy port, decide whether to kill it or rebind, and stop TIME_WAIT from blocking your restart.

---
"Address already in use" means another socket already owns that port. Find the owner with `sudo ss -ltnp 'sport = :8080'` (or `sudo lsof -i :8080`), then either kill the stale PID or start your server on another port. If nothing is listening and `bind()` still fails, a `TIME_WAIT` socket is in the way: set `SO_REUSEADDR` before binding.

```
$ ./myserver
error: listen tcp 0.0.0.0:8080: bind: address already in use
```

Node calls this `EADDRINUSE`, Python raises `OSError: [Errno 98]`, Go prints the line above, and uvicorn reports `[Errno 98] error while attempting to bind on address`. Same kernel error underneath: `bind(2)` returned `EADDRINUSE`.

## What the error actually means

A listening socket is a tuple: protocol, local address, local port. The kernel lets exactly one socket own a given `host:port` combination for a protocol at a time. When your process asks to `bind()` to `0.0.0.0:8080` and something already holds that tuple, the kernel refuses. It is not a firewall, not DNS, not permissions (usually). Something is already listening, or a recently closed socket is still holding the port.

Two questions get you to a fix: what owns the port, and do you want that thing gone or do you want a different port.

## Find the culprit

`ss` is the modern tool. `-l` listening, `-t` TCP, `-n` numeric, `-p` show the process:

```bash
$ sudo ss -ltnp 'sport = :8080'
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       511     0.0.0.0:8080        0.0.0.0:*          users:(("node",pid=4821,fd=23))
```

PID 4821, an old `node` process, still holds 8080. Run `ss` with `sudo`, otherwise the `Process` column comes back empty for sockets you do not own.

`lsof` gives the same answer plus the user, and `fuser` is the quickest when you just want the PID:

```bash
$ sudo lsof -nP -i :8080
COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node     4821  kiril   23u  IPv4  512377      0t0  TCP *:8080 (LISTEN)
$ sudo fuser 8080/tcp
8080/tcp:             4821
```

On Windows the equivalent is `netstat -ano | findstr :8080`, then `taskkill /PID 4821 /F`.

## Kill it or move

If the process is a stale copy of your own service, kill it:

```bash
$ sudo kill 4821          # SIGTERM, lets it clean up
$ sudo kill -9 4821       # SIGKILL, only if it ignores the first
$ sudo kill $(sudo lsof -t -i :8080)   # lookup and kill in one line
```

`fuser -k 8080/tcp` does the same. Be deliberate here: on a shared box that PID might be someone else's database. Confirm the command name before you signal anything.

If the port legitimately belongs to another service, do not fight it. Change your own port: `PORT=8081 ./myserver`, or `uvicorn main:app --port 8001`.

## The TIME_WAIT case: your just-stopped server can't rebind

You stop your server, immediately restart it, and get `address already in use` even though `ss -ltn` shows nothing listening. What you will see instead is a socket in `TIME_WAIT`:

```bash
$ ss -tan | grep 8080
TIME-WAIT  0  0  10.0.1.5:8080  10.0.1.9:52344
```

When a TCP connection closes, the side that closed first keeps the socket in `TIME_WAIT` for twice the maximum segment lifetime, a fixed 60 seconds on Linux. That lets straggling packets from the old connection die out. During that window the kernel, by default, refuses to bind a fresh listening socket to the same address and port.

The wrong fix is to wait, or to tune `tcp_fin_timeout` (which controls `FIN_WAIT_2`, not `TIME_WAIT`). The right fix lives in your code: set `SO_REUSEADDR` on the listening socket before you bind. Per `socket(7)`, it lets a socket bind "except when there is an active listening socket bound to the address".

```python
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # before bind()
s.bind(("0.0.0.0", 8080))
s.listen()
```

Most frameworks set this for you. Python's `http.server` (`allow_reuse_address = True`), Go's `net.Listen`, Node's `net`, and asyncio-based servers like uvicorn all enable it, which is why you rarely hit `TIME_WAIT` in those. If you wrote the socket code by hand and skipped it, this is your bug.

`SO_REUSEPORT` is different: it lets multiple sockets bind the *same* port at once so several workers can each accept connections, with the kernel load-balancing across them. All of them must set it and share the same effective UID. Reach for it when you want that behavior, not as a `TIME_WAIT` workaround.

## 0.0.0.0 vs 127.0.0.1 conflicts

`0.0.0.0` means every interface on this host; `127.0.0.1` means loopback only. A socket bound to `0.0.0.0:8080` covers `127.0.0.1:8080` too, so a second process trying `127.0.0.1:8080` collides with it. When `ss` shows `0.0.0.0:8080` and you thought you only bound loopback, that wildcard bind is the thing in your way.

## Privileged ports (below 1024)

Binding a port under 1024 as a normal user fails with `bind: permission denied`. That is `EACCES`, not `EADDRINUSE`, but it lands in the same "won't start on this port" bucket. Rather than run the whole service as root, grant just the bind capability:

```bash
$ sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/myserver
```

For interpreted apps, listen high (8080) and let a reverse proxy handle 80.

## The Docker cousin: "port is already allocated"

`docker run -p 8080:80 nginx` can fail with `Bind for 0.0.0.0:8080 failed: port is already allocated`, and Compose prints `failed to bind host port 0.0.0.0:8080/tcp: address already in use`. Same root cause, one layer up: something on the host, or another container's published port, already holds 8080. Use `ss -ltnp` on the host plus `docker ps` to spot the container. Full walkthrough: [Docker port already allocated](/blog/fix-docker-port-already-allocated).

## Questions people ask

### How do I fix "address already in use" with nc?

`nc -l 8080` fails when another listener, often a previous `nc` you backgrounded or left in another terminal, still holds the port. Run `sudo ss -ltnp 'sport = :8080'`, kill the PID it prints, and retry. If the port must stay with its current owner, listen on a free one instead. Netcat cannot share a port that has an active listener.

### Why does uvicorn say address already in use?

Uvicorn binds `127.0.0.1:8000` by default, so the usual cause is a second uvicorn still running: a forgotten terminal, a crashed `--reload` session that left a worker behind, or a container publishing 8000. Find it with `sudo lsof -i :8000`, kill that PID, or start on another port with `uvicorn main:app --port 8001`.

### How do I kill whatever is using a port?

`sudo kill $(sudo lsof -t -i :8080)` sends SIGTERM to every process holding 8080; `sudo fuser -k 8080/tcp` does the same in one command. Check the process name with `ss -ltnp` first, because on a shared host that PID might be a database or another team's service. Use `kill -9` only if SIGTERM is ignored.

## The call we'd make

Run `sudo ss -ltnp 'sport = :PORT'` first, every time. If the owner is a stale copy of your own service, kill it and restart. If it belongs to something real, move your service to another port instead of fighting for it. If nothing is listening but you still can't bind, fix `TIME_WAIT` with `SO_REUSEADDR` in the code rather than sleeping through the timer. Ninety percent of these are a forgotten background process, and `ss` finds it in seconds. For the broader toolkit, see our [Linux troubleshooting guide](/blog/linux-troubleshooting-guide).

## Sources

- [ss(8) Linux manual page: listening, process, and port filters](https://man7.org/linux/man-pages/man8/ss.8.html)
- [lsof(8) Linux manual page: -i, -n, -P, and -t options](https://man7.org/linux/man-pages/man8/lsof.8.html)
- [socket(7) Linux manual page: SO_REUSEADDR and SO_REUSEPORT](https://man7.org/linux/man-pages/man7/socket.7.html)
- [Node.js errors documentation: EADDRINUSE](https://nodejs.org/api/errors.html)

