Skip to content
ohsudo
Networking

"Address already in use": find who is using port 80 (or any port)

Fix "bind() failed (98: Address already in use)": find the process holding the port with ss or lsof, then stop it, move it, or change your port.

Beginner4 min readApplies to Linux with systemd · Ubuntu · Debian · RHEL / Rocky / AlmaLinux · nginx · Apache

by OhSudo ·

You start nginx and it refuses:

$ sudo systemctl start nginx
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.

The log has the real message:

nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()

Only one program can listen on a given address and port. Something else already holds port 80, so the operating system refuses to give it to nginx. The fix is always the same: find who holds the port, then decide which of the two should have it.

The same problem has a different message in every program:

Error: listen EADDRINUSE: address already in use :::3000 # Node.js
OSError: [Errno 98] Address already in use # Python
listen tcp :8080: bind: address already in use # Go
Bind for 0.0.0.0:80 failed: port is already allocated # Docker

TL;DR

Terminal window
sudo ss -tlnp | grep ':80 ' # who listens on port 80?
sudo lsof -i :80 -sTCP:LISTEN # the same, with lsof
sudo systemctl disable --now apache2 # stop it now and at boot (httpd on RHEL)
sudo systemctl start nginx

1. Read the error

Terminal window
sudo journalctl -u nginx -n 20 --no-pager

Note the address and port from the bind() line — 0.0.0.0:80 is port 80 on all IPv4 addresses, [::]:80 is port 80 on all IPv6 addresses. Error 98 means the port is taken. If you see error 13 (Permission denied) instead, it’s a different problem — see the end of this article.

2. Find the process that holds the port

Terminal window
sudo ss -tlnp | grep ':80 '
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=812,fd=6))

The options: -t TCP, -l listening sockets only, -n numbers instead of names (80, not http), -p the process. The last column is the answer: apache2, process ID 812.

Use sudo. Without it, ss still shows that the port is taken, but not who has it if the process belongs to another user — the users:(…) column is simply empty.

lsof gives the same answer, if it’s installed:

Terminal window
sudo lsof -i :80 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
apache2 812 root 4u IPv4 21534 0t0 TCP *:http (LISTEN)

The old netstat -tlnp also works where it’s still installed; on current distributions it’s usually missing, and ss replaces it.

3. Find out where it comes from

A process name and PID are not enough to fix it for good — you need to know what starts it. Ask systemd which unit the PID belongs to:

Terminal window
systemctl status 812

The first line names the unit, for example apache2.service. If the process is not part of any service (you see something like session-3.scope), someone started it by hand from a shell — ps -fp 812 shows the full command and its parent.

The usual suspects:

Who holds the port Why
apache2 / httpd Installed as a dependency, or left over from an older setup.
A second nginx Started by hand (sudo nginx) outside systemd, so systemd can’t start “its” nginx.
docker-proxy A container publishes the port (-p 80:80).
Your own app An old instance that never stopped.

4. Fix it

Another service has the port and you don’t need it. Stop it and keep it from starting at boot — otherwise the problem comes back after the next reboot, when the two services race for the port:

Terminal window
# Ubuntu / Debian
sudo systemctl disable --now apache2
# RHEL / Rocky / AlmaLinux
sudo systemctl disable --now httpd
sudo systemctl start nginx

If you are sure nothing needs the other web server, remove the package (sudo apt remove apache2 / sudo dnf remove httpd).

You need both services. Move one of them to a different port — for example Apache to 8080 (Listen 8080 in /etc/apache2/ports.conf on Debian/Ubuntu, /etc/httpd/conf/httpd.conf on RHEL) — and put nginx in front of it as a reverse proxy.

nginx was started by hand. systemd’s nginx can’t start because “your” nginx already has the port. Stop the stray one, then start it the normal way:

Terminal window
sudo nginx -s stop
sudo systemctl start nginx

A container has the port. Find it and stop it, or publish it on another port:

Terminal window
docker ps --filter publish=80

A process with no service. End it with kill:

Terminal window
sudo kill 812 # asks the process to exit (SIGTERM)
sudo kill -9 812 # forces it, only if it ignores the first one

Then check again with ss that the port is free before you start your service.

Warning

Don’t use kill on a process that belongs to a systemd service. systemd may restart it right away (with Restart=always), and you are back where you started. Stop the service with systemctl stop instead.

5. When nginx collides with itself

Sometimes ss shows the port is free, yet nginx still fails with error 98. Then nginx’s own configuration asks for the same address twice. A typical case: one server block has listen [::]:80 ipv6only=off; (IPv6 and IPv4), and another has listen 80; (IPv4). The second bind() collides with the first.

Find every listen line nginx uses:

Terminal window
sudo nginx -T 2>/dev/null | grep -n 'listen'

Keep IPv4 and IPv6 separate (listen 80; and listen [::]:80;, without ipv6only=off) and check the config before you start again:

Terminal window
sudo nginx -t && sudo systemctl start nginx

Shell, we have a problem!

Similar, but different: bind() to 0.0.0.0:80 failed (13: Permission denied) is not about a busy port. Ports below 1024 need root privileges (or the CAP_NET_BIND_SERVICE capability). This happens when the program runs as a normal user, or on RHEL when SELinux doesn’t allow that program on that port (sudo semanage port -l | grep http_port_t lists the allowed ones).

$ ./practice.sh

Practice this in the terminal

Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.

Open the terminal →

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles