← Back to blog

Guide

What is a port, actually? A practical explanation for developers

September 14, 20264 min read

You have typed localhost:3000 a thousand times. You have seen “port already in use” more often than you would like. But if someone asked you what a port actually is, you might hesitate.

Here is the short version, and then the parts that matter when things break.

A port is a number, nothing more

Your Mac has one IP address on a given network. Dozens of programs want to use that connection at the same time: a browser, a dev server, Docker, Spotify, a VPN. The IP address gets data to your machine, but it says nothing about which program should receive it.

The port is that second half of the address. An IP address gets the packet to the computer, the port number gets it to the right program.

It is a 16 bit number, which is why there are exactly 65,536 of them, from 0 to 65535. There is no hardware involved, no socket you can point at. It is a label the operating system uses to sort incoming data.

The rule that causes most of your problems

On a given address, only one process can listen on a port at a time.

That is the whole reason behind “address already in use”. It is not a bug, it is the operating system enforcing the rule that makes ports work at all. If two programs could listen on 3000, the kernel would have no way to decide which one gets an incoming connection.

So when your dev server refuses to start, nothing is broken. Something else got there first, usually a previous run of the same server that did not shut down cleanly.

Listening versus connecting

This trips people up, so it is worth separating.

A server listens on a port. It claims 3000 and waits. That is the exclusive one, and that is what a port checker shows you.

A client connects from a port. When your browser opens localhost:3000, it also uses a port on your side, picked automatically. Those are called ephemeral ports, and on macOS you can see the range the system draws from:

sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
# net.inet.ip.portrange.first: 49152
# net.inet.ip.portrange.last: 65535

You never think about those because you never choose them. When people talk about “what is using port 3000”, they almost always mean the listening side.

Ports below 1024 need root

Try to bind port 80 as a normal user and macOS stops you:

PermissionError: [Errno 13] Permission denied

Ports 0 to 1023 are privileged. Historically that was a security measure, the idea being that only a trusted process should be able to answer on the port everyone expects HTTP to be on.

This is why dev servers default to 3000, 5173, 8000 or 8080 instead of 80. Not convention for its own sake, just the lowest friction choice that does not require sudo.

TCP 3000 and UDP 3000 are different ports

TCP and UDP each have their own complete set of 65,536 ports. They do not collide. You can verify it:

# TCP 9955 is taken and listening
# binding UDP 9955 at the same time works fine

In practice almost everything you deal with locally is TCP, so this rarely comes up. It matters when you are reading tool output and wondering why a port looks both free and busy. Check which protocol you are looking at.

What this means when a port is stuck

Knowing the model makes the fix obvious. Something is listening, you want to know what, and then you want it gone.

lsof -i :3000

lsof starts at the port and gives you the process, which is the direction you need. Once you know the PID:

lsof -ti :3000 | xargs kill

-t prints just the process IDs, xargs hands all of them to one kill. That last part matters for servers that run several workers, since every worker holds the same port and you want all of them to go.

Going further

If you want the next layer, we wrote about what localhost really is and how the port ranges are divided up.

And if you would rather see your ports than type a command every time, Bosun keeps the list in your menu bar, with the process, the container and any open tunnel named properly. Free for 14 days, macOS 14 or later.

See this instead of typing it

Bosun lives in your menu bar and shows every open port on your Mac, live, mapped to the process behind it. One-click kill, SIGTERM first. Useful the first time this happens. Genuinely useful the fifth time it happens in one afternoon.