Address Already in Use: How to Fix Port Conflicts
You try to start your app. Instead of running, it immediately dies with an error like:
Error: listen EADDRINUSE: address already in use :::3000
Or maybe:
OSError: [Errno 98] Address already in use
bind: Address already in use
You Google it. You try restarting. Same error. You ask an AI and it tells you to change the port number. You change it and now your Nginx config is broken. Twenty minutes later you have two problems instead of one.
Here's what's actually happening and how to fix it properly.
What "Address already in use" means in plain English
Think of your server as a building. Each app runs behind a numbered door — that's the port. Your Node app might be behind door 3000, your database behind door 5432, your Redis cache behind door 6379.
Only one app can stand behind each door at a time. If your app tries to open door 3000 and something else is already there, your app says "address already in use" and gives up. It's not a crash. It's not a bug in your code. It just can't get to the door it needs because someone else is standing in it.
The error EADDRINUSE is the same thing — that's just the Node.js name for it. Python calls it OSError: Address already in use. Go, Ruby, Java — they all have their own wording for the same problem: the port is taken.
Why this happens
1. Your old app is still running
This is the most common cause by a mile. You hit Ctrl+C to stop your app, then started it again. But the old process didn't fully die. Now two copies are trying to use the same port, and the second one loses.
This happens a lot with Node apps, Python apps, and anything you run with npm start or python app.py directly in the terminal. You close the terminal tab, but the process keeps running in the background.
2. Systemd is running it AND you started it manually
You set up a systemd service to keep your app running (good idea). Then you forgot about it and started the app manually from the command line to test something. Now there are two copies: the systemd one on port 3000 and your manual one also trying to use port 3000. The manual one fails.
This one trips people up constantly because the systemd copy is invisible unless you check for it.
3. A zombie process from a crash
Your app crashed, but the operating system hasn't fully cleaned up yet. The process is dead but the port is still reserved for a few seconds (sometimes up to a minute). When you immediately restart, the port is still held by the ghost of the old process.
4. Two different apps using the same port
Your React dev server defaults to port 3000. Your Express API also defaults to port 3000. You start both. The second one fails. This is common in projects where the frontend and backend are in the same repo.
How to find what's using the port
Before you fix anything, find out what's actually on the port. Run one of these on your server:
lsof -i :3000
This shows you the process name and PID (process ID) of whatever is using port 3000. Replace 3000 with whatever port your error mentions.
If lsof doesn't work, try:
ss -tlnp | grep 3000
You'll see output like:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 12345 root 22u IPv6 654321 0t0 TCP *:3000 (LISTEN)
Now you know: it's a node process, PID 12345. That's the thing standing in your doorway.
How to fix it
Option 1: Kill the old process
If the thing on the port is a leftover copy of your own app, kill it:
kill 12345
Replace 12345 with the actual PID you found. Then start your app again. Done.
If kill doesn't work (the process refuses to die), escalate:
kill -9 12345
That force-kills it. Use this as a last resort — it doesn't give the process a chance to clean up after itself.
Option 2: Stop the systemd service first
If you find out systemd is running your app and you want to run it manually for testing, stop the service first:
sudo systemctl stop your-app-name
Then start it manually. When you're done testing, start the service again:
sudo systemctl start your-app-name
Don't leave both running. Pick one.
Option 3: Change the port (carefully)
If two different apps legitimately need to run at the same time, one of them needs a different port. But this isn't as simple as changing one number. If you move your app from port 3000 to port 8000, you also need to update:
- Your Nginx config — the
proxy_passline that sends traffic to your app - Your environment variables — any
.envfile orPORT=setting - Your Docker config — any port mappings in
docker-compose.ymlor yourDockerfile - Your firewall rules — if you opened port 3000 in your firewall, the new port needs to be open too
Change all of them or you'll trade this error for a different one.
Option 4: Wait a minute
If a crash left a zombie holding the port, sometimes the fix is to just wait 30-60 seconds. The OS will release the port on its own. Not glamorous, but it works.
Common ports and what uses them
If you're not sure what's supposed to be on a port, here's a quick reference:
- 3000 — Node.js, React dev server, Rails
- 5000 — Flask (Python)
- 8000 — Django, FastAPI, many Python frameworks
- 8080 — Common alternative HTTP port, Tomcat, Jenkins
- 5432 — PostgreSQL
- 6379 — Redis
- 3306 — MySQL
- 27017 — MongoDB
If lsof shows Postgres on port 5432, don't kill it — that's your database. You need it. The fix in that case is to change your app's port, not to kill the thing that's already there.
Why AI makes this worse
This is one of those errors where AI advice goes sideways fast. Here's the pattern:
- "Just change the port." The AI tells you to switch from port 3000 to 3001. Easy, right? Except now your Nginx config still points at 3000, so your site serves 502 errors. Your Docker compose still maps 3000. Your environment variable still says 3000. You've traded one problem for three.
- "Kill everything on the port." The AI tells you to run
kill -9 $(lsof -t -i:3000)without checking what's actually on the port. Maybe that's your database. Maybe it's another service that's supposed to be there. Blindly killing processes is how you create outages. - It doesn't ask why. A human engineer would ask: "Why are two things on the same port? Is this a systemd conflict? A stale process? A misconfigured Docker network?" The answer changes the fix. The AI just sees the error message and pattern-matches to a generic solution.
- It can't see your server. It doesn't know what's running, what systemd services exist, what your Nginx config says, or what your Docker setup looks like. So it guesses. And every wrong guess costs you time.
The right fix for "address already in use" takes about 30 seconds once you know what's on the port. The wrong fix — changing port numbers without updating everything that depends on them — can keep you stuck for hours.
Still can't figure out what's on your port?
Install MeatButton. Press it and a real expert will look at your actual server, find what's blocking the port, and fix the real problem. First one's free.
Get MeatButton