跪拜 Guibai
← Back to the summary

One Port, Two Listeners? SO_REUSEPORT vs SO_REUSEADDR, the Real Distinction

A few days ago a colleague asked me a fairly basic question: can the same port be listened on by two programs at the same time?

He thought and answered "No, a port can only be occupied by one program." That's not wrong, I used to answer the same. But then he asked a second question that stumped me: Nginx has several worker processes, why do they all listen on port 80 and run fine?

The answer is clearly not a simple yes or no. There's a set of socket binding rules hidden here worth going through carefully.

How the OS determines conflicts

First, a clear picture most people don't have. Our impression of a port is often a rough model: "one port corresponds to one program." But the kernel's judgment is more refined.

A program listening on a port is essentially doing a bind operation, binding itself to a combination of an IP + a port. The kernel maintains a binding table that records which addresses and ports are already occupied and by which socket.

So to determine whether two listens conflict, look at two things:

  1. The bound address plus port combination
  2. Whether the socket has any reuse-related options set

The second point is where the whole question turns.

By default, it indeed does not work

The default rule is straightforward: once a port has been bound as a listening state, if another program tries to bind the same address and port, the kernel returns an error directly, saying the address is already in use. That's the most common "port occupied" error, corresponding to the EADDRINUSE error code on Linux.

You can reproduce it with a few lines of Go:

ln1, err := net.Listen("tcp", ":8080")
// success
ln2, err := net.Listen("tcp", ":8080")
// second Listen will fail

Without any reuse option set, the kernel strictly guarantees the uniqueness of the same address plus port combination. Think about it: if no rule blocks it, the kernel could not decide which connection to hand to whom.

Two easily confused reuse switches

The kernel designers certainly knew the rigid rule would cause trouble, so they provided switches. There are two options here, names similar but duties completely different. I used to mix them up.

SO_REUSEADDR: address reuse

It solves the problem where a port in the waiting-recycling state cannot be immediately rebind. When a TCP connection closes, the actively closing side goes through TIME_WAIT state, usually waiting about 60 seconds. During this period the port is effectively still holding the remnants of the old connection. Without this, if you kill your service and restart immediately, you hit "port occupied" and have to wait a minute or two. So this option is almost standard in production network programming.

But let me be clear: SO_REUSEADDR only makes your restart faster; it does not allow two processes to listen on the same port simultaneously. The real job is done by the second.

SO_REUSEPORT: port reuse (Linux 3.9+)

With it enabled, multiple processes can actually bind and listen on the same port at the same time. The kernel's behavior changes then: for incoming connections to this port, the kernel distributes them to the listening processes based on a hash rule (usually based on the four-tuple of source address and source port). It's like the kernel itself carries a layer of load balancing.

Pseudo-code:

// After setting SO_REUSEPORT
ln1, _ := ListenWithReusePort("tcp", ":8080")
ln2, _ := ListenWithReusePort("tcp", ":8080")
// Both can listen on the same port normally

How much is this capability worth in a project?

Its significance is greater than imagined.

High-performance network services often use a multi-process model, like Nginx's workers. Each process enabling port reuse to listen on the same port has two benefits:

  1. All connections don't get squeezed into one process; the kernel spreads them out directly, so the single-process forwarding bottleneck disappears.
  2. If multiple processes share the same listening socket, there's a thundering herd problem when new connections arrive, or you need to use locks to coordinate. SO_REUSEPORT lets each process hold its own independent socket, eliminating the contention from the root.

In containerized environments there's a clever use case: zero-downtime restart. The old and new service processes first listen on the same port simultaneously, and after the new process is ready, the old one exits gracefully. The user side is completely unaware.

A pitfall I stepped into

By the way, one place easy to trip: if you start two service instances without enabling the reuse option, the second one will fail to start directly.

This is most common during container rolling releases. The new version container and the old version briefly coexist, both wanting to bind the same host port. If the code or config doesn't set SO_REUSEPORT, the new container can't start, the release gets stuck or even triggers a rollback.

When troubleshooting, remember to distinguish two cases:

Treat the symptom, don't try randomly.

Summing up

Back to the opening question, now we can answer it completely.

By default, the same address plus port combination can only be bound and listened by one program; repeating the bind reports "port occupied". With SO_REUSEPORT, multiple processes can listen on the same port simultaneously, and the kernel does hash distribution for inbound connections. This practice is common in multi-process high-performance services.

Also note: SO_REUSEADDR and SO_REUSEPORT are two different things, one controls fast restart, the other controls shared listening among multiple processes.

In the end, judging whether a listen conflicts is essentially about the bound address plus port combination, plus whether reuse flags are set. Being able to clearly separate these layers means you not only remember the conclusion, you truly understand the kernel mechanism. After all, stepping on the pit of container release port conflicts and just memorizing "a port can only be occupied by one program" are two completely different levels.

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

汪汪队首席上单

Can Java do this?