VPS Tutorials
VPS Port Already in Use (2026): Address already in use, EADDRINUSE, Docker/Nginx Conflict Troubleshooting
When starting a service on a VPS you see Address already in use, EADDRINUSE, bind() failed, or Docker port is already allocated. Common causes include an old process that never exited, Nginx/Apache/Caddy grabbing 80/443, Docker port mapping conflicts, IPv4/IPv6 dual-stack listening confusion, duplicate systemd service starts, or misreading TIME_WAIT. This article troubleshoots layer by layer using ss, lsof, fuser, journalctl, Docker, and Nginx logs.

A service fails to start on a VPS, and the log shows:
Address already in use
EADDRINUSE: address already in use
bind() to 0.0.0.0:80 failed (98: Address already in use)
port is already allocated
This kind of problem usually isn't that the code suddenly broke — it's that the port you want to listen on is already held by another process. The most common scenarios are: Nginx and Apache fighting over 80/443, a Node service started twice, a Docker container mapping the same port, or an old process that didn't exit cleanly.
Don't just run kill -9 right away. First confirm who is holding the port, then decide whether to stop the old service, change the port, modify the Docker mapping, or fix how systemd starts it.
On modern Linux, prefer ss:
sudo ss -lntp
To check only one port, for example 80:
sudo ss -lntp | grep ':80 '
To check 3000:
sudo ss -lntp | grep ':3000 '
In the output, focus on:
Local Address:Port: the listening address and port;users:(...): which process is holding it;pid=: the process ID;LISTEN: indicates this port is being listened on.
If you see:
LISTEN 0 511 0.0.0.0:80 users:(("nginx",pid=1234,fd=6))
that means port 80 is already taken by Nginx.
lsof is also very useful:
sudo lsof -i :3000
To show only the PID:
sudo lsof -t -i :3000
Typical output tells you:
- the command name;
- the PID;
- the user;
- IPv4 / IPv6;
- the LISTEN state.
If it isn't installed:
sudo apt install lsof -y
or:
sudo dnf install lsof -y
ss is better for system troubleshooting, while lsof is better for quickly seeing "which process is holding this port".
To view port usage:
sudo fuser -v 3000/tcp
To show only the PID:
sudo fuser 3000/tcp
If you've confirmed this process can be stopped, you can use:
sudo fuser -k 3000/tcp
But in production it's not recommended to kill directly like this. The safer approach is to first see which service the process belongs to:
ps -fp PID
systemctl status 服务名 --no-pager
If you can use systemctl stop, don't just kill -9.
The most common port conflict for web services is 80/443.
Check the port:
sudo ss -lntp | egrep ':80 |:443 '
Check the service status:
sudo systemctl status nginx --no-pager
sudo systemctl status apache2 --no-pager
sudo systemctl status caddy --no-pager
If Nginx reports:
bind() to 0.0.0.0:80 failed (98: Address already in use)
it's usually because Apache, Caddy, another Nginx, or a Docker container already holds 80.
The solution is not to "kill them all":
- keep only one web entry point;
- move the other service to 8080/8443;
- let Nginx/Caddy act as a reverse proxy;
- disable the unused service:
sudo systemctl stop apache2
sudo systemctl disable apache2
After changing the Nginx config:
sudo nginx -t
sudo systemctl reload nginx
For Nginx configuration errors, see: VPS Nginx Configuration Errors.
Docker errors usually look like this:
Bind for 0.0.0.0:8080 failed: port is already allocated
First check container port mappings:
docker ps --format "table {{.Names}}\t{{.Ports}}"
Then check host ports:
sudo ss -lntp | grep ':8080 '
Common causes:
- two containers both specify
-p 8080:80; - host Nginx already occupies 80, and the container also wants
-p 80:80; - an old container wasn't removed and still holds the port;
- docker-proxy or Docker network state is abnormal.
How to handle it:
docker ps -a
docker stop 容器名
docker rm 容器名
Or change the port mapping:
ports:
- "8081:80"
Don't just map multiple services to 80 to get them running. On a single host IP, the same port can only be held by one listener at a time.
If the service is managed by systemd, first check:
sudo systemctl status app --no-pager
sudo journalctl -u app -n 100 --no-pager
Common log:
Error: listen EADDRINUSE: address already in use :::3000
Failed with result 'exit-code'
This means the application failed to bind the port at startup; systemd is only reporting that the service failed, not that systemd itself has a problem.
Troubleshooting order:
sudo ss -lntp | grep ':3000 '
ps -fp PID
sudo systemctl stop 冲突服务
sudo systemctl restart app
If the service restarts repeatedly, it may also trigger start-limit-hit. In that case, fix the port conflict first, then:
sudo systemctl reset-failed app
sudo systemctl start app
For more detailed systemd startup failure troubleshooting, see: systemd Service Fails to Start on VPS.
Sometimes you clearly see only one service, yet it still reports a port conflict — the cause may be IPv4/IPv6 dual-stack listening.
Check listening addresses:
sudo ss -lntp | grep ':80 '
You may see:
0.0.0.0:80
[::]:80
The meaning is roughly:
0.0.0.0:80: listen on all IPv4 addresses;[::]:80: listen on IPv6 addresses;- under some configurations
[::]:80may also accept IPv4 at the same time.
In Nginx it's recommended to write explicitly:
listen 80;
listen [::]:80;
If you only want to listen on IPv4:
listen 0.0.0.0:80;
Don't let one service listen on 0.0.0.0:80 while another listens on [::]:80, and then expect them to stay out of each other's way. Actual behavior depends on the system and application configuration, and it easily causes troubleshooting confusion.
Many people see a pile of TIME_WAIT and assume the port is occupied.
Check:
ss -tan | grep TIME-WAIT | wc -l
TIME_WAIT is a normal TCP state, usually related to many short-lived connections. It generally does not prevent a service from listening on a fixed port like 0.0.0.0:80.
What really needs attention is:
sudo ss -lntp
whether any process is in the LISTEN state holding the target port.
Don't casually change tcp_tw_reuse, tcp_fin_timeout. If it's ephemeral port exhaustion, that's a different kind of problem; if the service reports EADDRINUSE at startup, check the LISTEN process first.
Common in Node.js:
Error: listen EADDRINUSE: address already in use :::3000
Troubleshoot:
sudo ss -lntp | grep ':3000 '
If it's a development environment, you can change the port:
PORT=3001 npm start
Python / Flask / FastAPI are similar:
uvicorn app:app --host 0.0.0.0 --port 8001
In production, don't let services randomly change ports. Either fix the port and release the conflicting process, or have Nginx reverse proxy to the new port and update the configuration accordingly.
The common command found online:
sudo kill -9 $(sudo lsof -t -i:3000)
It does release the port, but the risk is: you may kill a process that is serving traffic, a database proxy, a reverse proxy, or someone else's background job.
A safer process:
sudo lsof -i :3000
ps -fp PID
systemctl status 相关服务 --no-pager
After confirming it's correct, then:
sudo systemctl stop 服务名
Only when you've confirmed it's not a systemd service and not a critical process should you consider kill.
When you encounter Address already in use, EADDRINUSE, port is already allocated, check in this order:
sudo ss -lntp | grep ':端口 'to find the LISTEN process.sudo lsof -i :端口to see the process name, user, and PID.ps -fp PIDto confirm what it is.- If it's a systemd service, first
systemctl status 服务名. - If it's Docker,
docker ps --format "table {{.Names}}\t{{.Ports}}". - If it's 80/443, check whether Nginx, Apache, or Caddy conflict.
- Check
journalctl -u 服务名 -n 100to confirm the application error. - If necessary, change the port, stop the conflicting service, or change the Docker mapping.
- Don't treat TIME_WAIT as the main cause of listening port conflicts.
- Don't blindly
kill -9; confirm process ownership first.
The core of troubleshooting an occupied port is simple: first find who is LISTENing, then decide how to handle it.
ss -lntp is the first priority, lsof -i :端口 is used to confirm the process, and fuser can quickly locate and release. Nginx/Apache/Caddy fighting over 80/443, Docker port mapping conflicts, and Node/Python services started twice are the most common causes on a VPS.
Don't mix up TIME_WAIT, firewalls, security groups, and port listening. Address already in use refers to a local port binding conflict; firewalls and security groups affect whether the outside can access it, which is a different kind of problem.