How to Avoid Conflicts and Let Your OS Select a Random Port

by Christoph Schiessl on Python, FastAPI, and DevOps

As software engineers, we frequently start various server processes that open up specific ports to listen to incoming requests. In most cases, we want to select a known port number by hand since we want to interact with those server processes manually. For web developers, the most common type of service is HTTP servers. For instance, I usually start my Python web application to listen on port 8000, then use my browser or an HTTP client like httpie in the console to feed HTTP requests to it. Sometimes, however, we don't care about port numbers — on the contrary, we don't want to write down the port number because hard-coding would limit us from running multiple instances of the same server in parallel.

To give a concrete example, imagine a web server that's only started to run E2E tests in a web browser. There's no way around starting a real server because the remote-controlled browser is a different process. The two processes need a way to communicate with each other. For web browsers, the networking stack is the way to talk to other processes, specifically HTTP servers. Long story short, a server process with an open port to listen for HTTP requests from the web browser is needed.

This may seem far-fetched, but a hard-coded port number is suboptimal for this use case because it removes the possibility of running multiple instances of the same test suite in parallel. You may not do this often, but testing two feature branches in parallel to check against regressions isn't too hard to imagine.

The solution is to build your server processes to avoid the need for resources that can only ever be acquired by a single process. One example of such a resource is specific port numbers — there can only ever be one, and only one process that can bind to any given port number. Another way to say this is that ports, by definition, cannot be shared between processes.

Playing with the socket module

Before doing anything else, I want to demonstrate the problem when two processes try to bind to the same port number. For that, we are using the socket module from Python's standard library.

Firstly, I create a new socket object with the help of the socket() function. This object represents an IPv4 socket that cannot yet do anything useful because it's not yet bound to any port number.

Secondly, I used the socket object's bind() method to attempt to bind it to port number 8000. As you can see, this raised an OSError because a different process on my system was already bound to the same port number, confirming that ports are exclusive and cannot be shared between processes.

Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket()
>>> sock.bind(('', 8000))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 98] Address already in use

I quickly want to mention that the parameter of the bind() method depends on the socket's address family, which I didn't specify. Therefore, it uses the default, which is IPv4. Anyway, for IPv4 sockets, the parameter must be of the type tuple[str, int]. The str selects the interface, and the int selects the port number to which to bind.

So, what is an interface? Generally speaking, computers can have an arbitrary number of interfaces with different IP addresses. Most computers have at least two: a loopback interface with IP address, which your computer uses to talk to processes that it is running itself and a "real" interface with actual networking hardware with which your computer talks to other computers.

I used an empty string to select the interface in the example above. This special value tells the socket to bind to the given port number on all of the computer's interfaces. Knowing this, you now also have a complete interpretation of the raised OSError. Namely, the error really means that port 8000 was already bound to a different socket for at least one of my computer's interfaces.

Letting your OS select a Random Port

So, how can we get around this? How can you tell in advance if a given port number is already in use? You don't have to because you can tell your operating system, which must be aware of all bound ports, to randomly select a port number for you. To demonstrate this, we can again use Python's low-level socket module:

Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> sock = socket.socket()
>>> sock.bind(('', 0))
>>> sock.getsockname()
('', 45913)

As before, I'm creating a socket object, but I bind to port 0 instead of port 8000. The range of possible ports goes from 0 to 2 ** 16 - 1 == 65535, but some port numbers have special meaning. Port number 0 is one of these special numbers that makes your operating system randomly select a free port number for you.

Once the socket is successfully bound to a port number, we can use the getsockname() method to determine which port the OS has selected. The documentation explains this method as follows:

Return the socket’s own address. This is useful for finding the port number of an IPv4/v6 socket, for instance. (The format of the address returned depends on the address family—see above.)

As you can see above, the function returns a value of type tuple[str, int] because the socket uses the IPv4 address family. In this case, it tells us that the socket is bound to (i.e., all interfaces) and port 45913.

Starting FastAPI apps with a Random Port

The cool thing about this is that it's pretty low-level functionality, which is usually also available through high-level APIs. For instance, if you use uvicorn to serve your FastAPI application, you can ask it to bind to port 0. This is then passed through to the underlying socket implementation of your OS and ultimately results in the selection of a random port ...

import uvicorn
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()

@app.get("/hello", response_class=PlainTextResponse)
async def hello():
    return "Hello World"

if __name__ == "__main__":
    # uvicorn uses port 8000 by default, but we're running it
    # with port 0 to trigger the random port selection of the OS., port=0)
$ python
INFO:     Started server process [977021]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on (Press CTRL+C to quit)
INFO: - "GET /hello HTTP/1.1" 200 OK
$ http GET
HTTP/1.1 200 OK
content-length: 10
content-type: text/plain; charset=utf-8
date: Sun, 28 Apr 2024 14:10:00 GMT
server: uvicorn

Hello World

In this case, my OS selected port number 41507, but the next time the application starts, it will randomly select a different one. You can also run the same application multiple times in parallel, and each process will automatically pick a different port number.

That's everything for today. Thank you for reading, and see you soon!

Ready to Learn More Web Development?

Join my Mailing List to receive two articles per week.

I send two weekly emails on building performant and resilient Web Applications with Python, JavaScript and PostgreSQL. No spam. Unscubscribe at any time.

Continue Reading?

Here are a few more Articles for you ...

The Built-in id() Function

Learn about object identities and comparisons in Python. Discover the built-in id() function, the is and is not operators, and more.

By Christoph Schiessl on Python

Disabling 304 Not Modified in FastAPI's StaticFiles

Dealing with caching issues in FastAPI's StaticFiles sub-application and a monkey patching workaround to disable caching.

By Christoph Schiessl on Python and FastAPI

How to Find the First/Last Day of a Year from a given Date

Learn how to find the first and last day of a year with Python's datetime module. This article explains step by step what you need to know.

By Christoph Schiessl on Python

Christoph Schiessl

Christoph Schiessl

Independent Consultant + Full Stack Developer

If you hire me, you can rely on more than a decade of experience, which I have collected working on web applications for many clients across multiple industries. My involvement usually focuses on hands-on development work using various technologies like Python, JavaScript, PostgreSQL, or whichever technology we determine to be the best tool for the job. Furthermore, you can also depend on me in an advisory capacity to make educated technological choices for your backend and frontend teams. Lastly, I can help you transition to or improve your agile development processes.