Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Linux Admins By James Joyner IV · · 9 min read

Linux Error: bind: Address already in use — Cause, Fix, and Troubleshooting Guide

How to fix bind: Address already in use (EADDRINUSE) on Linux. Find the listener with ss and lsof, clear TIME_WAIT, and untangle systemd socket conflicts.

  • #linux
  • #troubleshooting
  • #errors
  • #networking

Summary

bind: Address already in use (errno EADDRINUSE) is returned when a process calls bind() on an IP/port pair the kernel already considers claimed. The socket layer refuses to let two endpoints hold the same address tuple, so the second binder fails and the service dies on launch. It is one of the most common reasons a daemon exits immediately on start. The conflict is per address tuple, so 127.0.0.1:8080 and 0.0.0.0:8080 collide, while 10.0.0.5:8080 and 10.0.0.6:8080 do not.

Common Symptoms

  • A service exits immediately on start with Address already in use / EADDRINUSE.
  • nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) in the logs.
  • systemctl status shows the unit failed (Result: exit-code) right after start.
  • A restart fails, but a wait-and-retry sometimes succeeds (TIME_WAIT clearing).
  • Two copies of the daemon appear briefly, or an old PID still owns the port.

Most Likely Causes of the ‘bind: Address already in use’ Error

Most production bind: Address already in use failures come from these, roughly in order of frequency:

  1. Another process already listens on the port — an unrelated service (or an earlier deploy) holds the tuple.
  2. The same service started twice — a double start, a supervisor relaunch that did not kill the old worker, or an ExecReload race left a prior instance bound.
  3. Socket stuck in TIME_WAIT with no SO_REUSEADDR — after a close, the listening tuple lingers and an immediate restart fails until the timer expires.
  4. A previous instance not fully terminated — a killed-but-not-reaped process or a child that inherited the listening socket keeps the port claimed (often re-parented to PPID 1).
  5. A 0.0.0.0 wildcard bind overlapping a specific-IP bind0.0.0.0:443 conflicts with 10.0.0.5:443 on the same port, in either direction.
  6. A systemd socket-activation .socket unit already holding the port, colliding with the daemon’s own bind().

Quick Triage

# Who owns the port right now? (PID, command, bound address)
ss -ltnp 'sport = :8080'

# Is it a live listener or just stale connections?
ss -tan 'sport = :8080' | head

# Anything holding the socket that ss missed?
sudo fuser -v 8080/tcp

A LISTEN row means a live owner; only TIME-WAIT/CLOSE-WAIT rows mean a stale tuple that will clear on its own.

Diagnostic Commands

# Authoritative owner + bound local address (specific IP vs 0.0.0.0)
ss -ltnp 'sport = :<PORT>'
sudo lsof -iTCP:<PORT> -sTCP:LISTEN -P -n

ss/lsof name the PID, command, and the exact local address bound — note whether it is a specific IP or the wildcard.

# Duplicate instances of your own service
pgrep -a <service>
ps -o pid,ppid,stat,cmd -p "$(fuser <PORT>/tcp 2>/dev/null)"

Two PIDs of the same service, or a re-parented orphan (PPID 1), points at a double-start or unclean shutdown.

# systemd socket-activation conflicts
systemctl list-sockets | grep ':<PORT>'
systemctl status <service>.socket 2>/dev/null

If a .socket unit owns the port, the daemon should not also bind it. ss will show users:(("systemd",pid=1,...)) when systemd holds the socket. On Ubuntu/Debian and RHEL/Rocky the commands are identical; only the unit file paths (/lib/systemd/system vs /usr/lib/systemd/system) differ.

Fix / Remediation

  1. Confirm the owner and its bound address with ss -ltnp 'sport = :<PORT>'. Decide whether the claim is legitimate, stale, or your own duplicate.
  2. If it is a stale TIME_WAIT tuple, simply retry after a few seconds, or enable SO_REUSEADDR on the listening socket in the app — nearly every server framework exposes this. The kernel clears TIME_WAIT on its own.
  3. If a rightful service owns the port, reconfigure your daemon to a free port, or stop the existing service cleanly with systemctl stop <service>.
  4. If a .socket unit holds it, systemctl stop/disable the conflicting socket unit, or let the activated service own the socket instead of binding it again.
  5. If it is a stray or orphaned copy of your own service, kill it, then restart through the manager.

Warning: fuser -k and kill terminate whatever holds the socket, including a legitimate production listener. Confirm the PID and command first — fuser -k is a last resort.

sudo fuser -k <PORT>/tcp          # last resort: kill the socket holder
sudo systemctl restart <service>

Validation

# Exactly one LISTEN on the expected address, no stray TIME_WAIT pileup
ss -ltnp 'sport = :<PORT>'
ss -tan  'sport = :<PORT>' | head

# Service is active and stayed up
systemctl status <service> --no-pager

One LISTEN row on the intended local address, and a unit that stays active (running), confirms the port is cleanly claimed.

Prevention

  • Enable SO_REUSEADDR on listening sockets so a fast restart over a TIME_WAIT tuple does not fail.
  • Set KillMode=mixed (or control-group) and a definite ExecStop in systemd units so no orphaned child survives to squat the port after a stop or failed restart.
  • Standardize port assignments in config management (Ansible/Puppet) and add a pre-start ss -ltnp check to catch collisions before launch.
  • Decide deliberately between socket-activation (.socket units) and in-app binding for a given port — never both.
  • Bind to specific addresses consistently across a host so a 0.0.0.0 listener and a specific-IP listener never silently overlap.
  • Monitor for unexpected listeners and alert on services flapping in a restart loop.

Final Notes

bind: Address already in use means the kernel already considers your IP/port tuple claimed. Work from ss -ltnp to the owning PID and its bound address before touching anything — once you know whether the claim is live, stale, or your own duplicate, the remedy is immediate: stop the rightful listener, wait out a stale TIME_WAIT, or kill a stray orphan.

Want faster Linux incident response? Use DevOps AI Toolkit to turn production errors into clear diagnostics, remediation steps, and reusable runbooks.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.