MeatButton

Address Already in Use: How to Fix Port Conflicts

For anyone whose app won't start because of a port conflict

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:

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:

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:

  1. "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.
  2. "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.
  3. 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.
  4. 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