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 127.0.0.1, 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()
('0.0.0.0', 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 0.0.0.0 (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.
    uvicorn.run(app=app, port=0)
$ python app.py
INFO:     Started server process [977021]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:41507 (Press CTRL+C to quit)
INFO:     127.0.0.1:41507 - "GET /hello HTTP/1.1" 200 OK
$ http GET http://127.0.0.1:41507/hello
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!

Web App Reverse Checklist

Ready to Build Your Next Web App?

Get my Web App Reverse Checklist first ...


Software Engineering is often driven by fashion, but swimming with the current is rarely the best choice. In addition to knowing what to do, it's equally important to know what not to do. And this is precisely what my free Web App Reverse Checklist will help you with.

Subscribe below to get your free copy of my Reverse Checklist delivered to your inbox. Afterward, you can expect one weekly email on building resilient Web Applications using Python, JavaScript, and PostgreSQL.

By the way, it goes without saying that I'm not sharing your email address with anyone, and you're free to unsubscribe at any time. No spam. No commitments. No questions asked.

Continue Reading?

Here are a few more Articles for you ...


Starting and Stopping uvicorn in the Background

Learn how to start and stop uvicorn in the background using a randomly selected free port number. Useful for running test suites that require live-webservers.

By Christoph Schiessl on Python

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

Restarting uvicorn Workers with the SIGHUP Signal

Learn about process management in Python's uvicorn web server and how to use signals to restart workers and to increment/decrement the number of workers.

By Christoph Schiessl on Python

Christoph Schiessl

Hi, I'm Christoph Schiessl.

I help you build robust and fast Web Applications.


I'm available for hire as a freelance web developer, so you can take advantage of my more than a decade of experience working on many projects across several industries. Most of my clients are building web-based SaaS applications in a B2B context and depend on my expertise in various capacities.

More often than not, my involvement includes hands-on development work using technologies like Python, JavaScript, and PostgreSQL. Furthermore, if you already have an established team, I can support you as a technical product manager with a passion for simplifying complex processes. Lastly, I'm an avid writer and educator who takes pride in breaking technical concepts down into the simplest possible terms.